Added miltipart archives support

This commit is contained in:
nikita.kosilo
2020-10-28 11:57:17 +03:00
parent 31c2a1b403
commit f0aac28887
105 changed files with 11493 additions and 41 deletions

View File

@@ -48,15 +48,23 @@ Syntax sugar for the event handlers (onTick, onStop, onError)
eventName: `error`
### Methods
#### `zip(path, files, onSuccess?, onError?): void`
#### `zip(path, files, options, onSuccess?, onError?): void`
Establishes connection with the remote host.
| parameter | type | description |
| ----------- |-----------------------------|--------------|
| `path` | `string` | zip archive path | |
| `files` | `string array` | path to files |
| `options` | `options object or null` | path to files |
| `onSuccess` | `() => void` | Success callback - called after archive was created. (optional)|
| `onError` | `(message: string) => void` | Error callback - called when some error occurs during creating an archive. (optional)|
#### Options:
| parameter | type | description |
| ----------- |-----------------------------|--------------|
| `maxSize` | `float` | File size, in Mb. Default 0 - no max size |
## What's new
- 1.0.0 - initial code
- 1.0.0 - initial code
- 1.0.1 - cleared zip instances
- 2.0.0 - added max file size and zip parts support, updated SSZipArchive version

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-archive-zip",
"version": "1.0.0",
"version": "2.0.0",
"description": "Cordova Zip Archive Plugin",
"types": "./types/index.d.ts",
"cordova": {

View File

@@ -18,7 +18,7 @@
under the License.
-->
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="cordova-plugin-archive-zip" version="1.0.0">
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="cordova-plugin-archive-zip" version="2.0.0">
<name>Zip archive</name>
<description>Cordova zip archive plugin</description>
<license>Apache 2.0</license>
@@ -26,6 +26,8 @@
<repo>https://github.com/kitolog/cordova-plugin-zip-archive</repo>
<issue>https://github.com/kitolog/cordova-plugin-zip-archive</issue>
<dependency id="cordova-plugin-cocoapod-support"/>
<engines>
<engine name="cordova-android" version=">=3.6.0"/><!-- Requires CordovaPlugin.preferences -->
<engine name="cordova-ios" version=">=4.0.0"/>
@@ -43,10 +45,14 @@
</feature>
</config-file>
<!-- <framework src="net.lingala.zip4j:2.6.4" />-->
<source-file src="src/android/ZipArchiveAdapter.java" target-dir="src/com/hqsoftwarelab/ziparchive"/>
<source-file src="src/android/ZipArchiveAdapterImpl.java" target-dir="src/com/hqsoftwarelab/ziparchive"/>
<source-file src="src/android/ZipArchivePlugin.java" target-dir="src/com/hqsoftwarelab/ziparchive"/>
<source-file src="src/android/Consumer.java" target-dir="src/com/hqsoftwarelab/ziparchive"/>
<source-file src="src/android/net/lingala/zip4j" target-dir="src/main/java/net/lingala"/>
</platform>
<!-- ios -->
@@ -58,7 +64,8 @@
</feature>
</config-file>
<framework src="SSZipArchive" type="podspec" spec="~> 2.0.0" />
<!-- <framework src="SSZipArchive" type="podspec" spec="~> 2.2.3" />-->
<pod name="SSZipArchive" git="https://github.com/kitolog/ZipArchive" branch="master" />
<header-file src="src/ios/ZipArchiveAdapter.h"/>
<source-file src="src/ios/ZipArchiveAdapter.m"/>

View File

@@ -22,7 +22,7 @@ import java.io.IOException;
import java.util.ArrayList;
public interface ZipArchiveAdapter {
public void zip(String zipFile, ArrayList<String> files) throws IOException;
public void zip(String zipFile, ArrayList<String> files, float maxSize) throws IOException;
// public void zip(String zipFile, String[] files) throws IOException;
public void stop();
public void setZipEventHandler(Consumer<String> startEventHandler);

View File

@@ -29,6 +29,10 @@ import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
public class ZipArchiveAdapterImpl implements ZipArchiveAdapter {
private Consumer<String> zipEventHandler;
@@ -49,40 +53,58 @@ public class ZipArchiveAdapterImpl implements ZipArchiveAdapter {
private static int BUFFER_SIZE = 6 * 1024;
@Override
public void zip(String zipFile, ArrayList<String> filesList) throws IOException {
public void zip(String zipFilePath, ArrayList<String> filesList, float maxSize) throws IOException {
FileOutputStream fileOutputStream = null;
ZipOutputStream zipOutputStream = null;
FileInputStream fileInputStream = null;
if (filesList.isEmpty())
invokeExceptionHandler("No files found");
try {
zipFile = zipFile.replace("file:///", "/");
File file = new File(zipFile);
if (!file.exists()) {
file.createNewFile();
zipFilePath = zipFilePath.replace("file:///", "/");
File file = new File(zipFilePath);
if (file.exists() && file.isFile()) {
if (file.delete()) {
System.out.println("Zip file DELETED :" + zipFilePath);
} else {
System.out.println("Zip file not deleted :" + zipFilePath);
}
} else {
System.out.println("Zip file not exists :" + zipFilePath);
}
fileOutputStream = new FileOutputStream(file, false);
zipOutputStream = new ZipOutputStream(fileOutputStream);
File parentDir = file.getParentFile();
if (parentDir.exists() && parentDir.isDirectory()) {
File[] dirFiles = parentDir.listFiles();
if ((dirFiles != null) && (dirFiles.length > 0)) {
System.out.println("Zip file parent dir files count :" + dirFiles.length);
for (int i = 0; i < dirFiles.length; i++) {
String fileName = dirFiles[i].getName();
if (dirFiles[i].isFile() && fileName.matches(".*\\.z.*")) {
System.out.println("Zip delete old chunks : " + fileName);
if (dirFiles[i].delete()) {
System.out.println("Zip file chunk DELETED :" + fileName);
} else {
System.out.println("Zip file chunk not deleted :" + fileName);
}
}
}
}
}
ArrayList<File> preparedFilesList = new ArrayList<File>();
for (String filePath : filesList) {
filePath = filePath.replace("file:///", "/");
fileInputStream = new FileInputStream(filePath);
ZipEntry zipEntry = new ZipEntry(filePath.substring(filePath.lastIndexOf("/") + 1));
zipOutputStream.putNextEntry(zipEntry);
byte[] tmp = new byte[BUFFER_SIZE];
int size = 0;
while ((size = fileInputStream.read(tmp)) != -1) {
zipOutputStream.write(tmp, 0, size);
}
zipOutputStream.flush();
fileInputStream.close();
preparedFilesList.add(new File(filePath));
}
zipOutputStream.close();
fileOutputStream.close();
invokeZipEventHandler(zipFile);
} catch (FileNotFoundException e) {
int fileSize = Math.round(maxSize * 1024 * 1024);
ZipFile zipFile = new ZipFile(file);
zipFile.createSplitZipFile(preparedFilesList, new ZipParameters(), true, fileSize); // using 10MB in this example
invokeZipEventHandler(zipFilePath);
} catch (ZipException e) {
e.printStackTrace();
invokeExceptionHandler(e.getMessage());
} catch (IOException e) {
e.printStackTrace();

View File

@@ -51,6 +51,7 @@ public class ZipArchivePlugin extends CordovaPlugin {
String adapterKey = args.getString(0);
String zipFile = args.getString(1);
JSONArray jsonArray = args.getJSONArray(2);
int maxSize = args.getInt(3);
ArrayList<String> filesArray = new ArrayList<String>();
for (int i = 0, count = jsonArray.length(); i < count; i++) {
try {
@@ -68,7 +69,7 @@ public class ZipArchivePlugin extends CordovaPlugin {
zipArchiveAdapter.setErrorEventHandler(new ErrorEventHandler(adapterKey));
zipArchiveAdapter.setZipEventHandler(new ZipEventHandler(adapterKey, zipArchiveAdapter, callbackContext));
try {
zipArchiveAdapter.zip(zipFile, filesArray);
zipArchiveAdapter.zip(zipFile, filesArray, maxSize);
} catch (IOException e) {
e.printStackTrace();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
/*
* 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 net.lingala.zip4j.crypto;
import net.lingala.zip4j.crypto.PBKDF2.MacBasedPRF;
import net.lingala.zip4j.crypto.engine.AESEngine;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.AESExtraDataRecord;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import java.util.Arrays;
import static net.lingala.zip4j.crypto.AesCipherUtil.prepareBuffAESIVBytes;
import static net.lingala.zip4j.util.InternalZipConstants.AES_BLOCK_SIZE;
/**
* AES Decrypter supports AE-1 and AE-2 decryption for AES-CTR with 128, 192, or 256 Key Strength
*/
public class AESDecrypter implements Decrypter {
private AESExtraDataRecord aesExtraDataRecord;
private char[] password;
private AESEngine aesEngine;
private MacBasedPRF mac;
private int nonce = 1;
private byte[] iv;
private byte[] counterBlock;
public AESDecrypter(AESExtraDataRecord aesExtraDataRecord, char[] password, byte[] salt, byte[] passwordVerifier) throws ZipException {
this.aesExtraDataRecord = aesExtraDataRecord;
this.password = password;
iv = new byte[AES_BLOCK_SIZE];
counterBlock = new byte[AES_BLOCK_SIZE];
init(salt, passwordVerifier);
}
private void init(byte[] salt, byte[] passwordVerifier) throws ZipException {
if (password == null || password.length <= 0) {
throw new ZipException("empty or null password provided for AES decryption");
}
final AesKeyStrength aesKeyStrength = aesExtraDataRecord.getAesKeyStrength();
final byte[] derivedKey = AesCipherUtil.derivePasswordBasedKey(salt, password, aesKeyStrength);
final byte[] derivedPasswordVerifier = AesCipherUtil.derivePasswordVerifier(derivedKey, aesKeyStrength);
if (!Arrays.equals(passwordVerifier, derivedPasswordVerifier)) {
throw new ZipException("Wrong Password", ZipException.Type.WRONG_PASSWORD);
}
aesEngine = AesCipherUtil.getAESEngine(derivedKey, aesKeyStrength);
mac = AesCipherUtil.getMacBasedPRF(derivedKey, aesKeyStrength);
}
@Override
public int decryptData(byte[] buff, int start, int len) throws ZipException {
for (int j = start; j < (start + len); j += AES_BLOCK_SIZE) {
int loopCount = (j + AES_BLOCK_SIZE <= (start + len)) ?
AES_BLOCK_SIZE : ((start + len) - j);
mac.update(buff, j, loopCount);
prepareBuffAESIVBytes(iv, nonce);
aesEngine.processBlock(iv, counterBlock);
for (int k = 0; k < loopCount; k++) {
buff[j + k] = (byte) (buff[j + k] ^ counterBlock[k]);
}
nonce++;
}
return len;
}
public byte[] getCalculatedAuthenticationBytes() {
return mac.doFinal();
}
}

View File

@@ -0,0 +1,152 @@
/*
* 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 net.lingala.zip4j.crypto;
import net.lingala.zip4j.crypto.PBKDF2.MacBasedPRF;
import net.lingala.zip4j.crypto.engine.AESEngine;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import java.security.SecureRandom;
import static net.lingala.zip4j.crypto.AesCipherUtil.prepareBuffAESIVBytes;
import static net.lingala.zip4j.util.InternalZipConstants.AES_BLOCK_SIZE;
/**
* AES Encrypter supports AE-1 and AE-2 encryption using AES-CTR with either 128 or 256 Key Strength
*/
public class AESEncrypter implements Encrypter {
private char[] password;
private AesKeyStrength aesKeyStrength;
private AESEngine aesEngine;
private MacBasedPRF mac;
private SecureRandom random = new SecureRandom();
private boolean finished;
private int nonce = 1;
private int loopCount = 0;
private byte[] iv;
private byte[] counterBlock;
private byte[] derivedPasswordVerifier;
private byte[] saltBytes;
public AESEncrypter(char[] password, AesKeyStrength aesKeyStrength) throws ZipException {
if (password == null || password.length == 0) {
throw new ZipException("input password is empty or null");
}
if (aesKeyStrength != AesKeyStrength.KEY_STRENGTH_128 &&
aesKeyStrength != AesKeyStrength.KEY_STRENGTH_256) {
throw new ZipException("Invalid AES key strength");
}
this.password = password;
this.aesKeyStrength = aesKeyStrength;
this.finished = false;
counterBlock = new byte[AES_BLOCK_SIZE];
iv = new byte[AES_BLOCK_SIZE];
init();
}
private void init() throws ZipException {
saltBytes = generateSalt(aesKeyStrength.getSaltLength());
byte[] derivedKey = AesCipherUtil.derivePasswordBasedKey(saltBytes, password, aesKeyStrength);
derivedPasswordVerifier = AesCipherUtil.derivePasswordVerifier(derivedKey, aesKeyStrength);
aesEngine = AesCipherUtil.getAESEngine(derivedKey, aesKeyStrength);
mac = AesCipherUtil.getMacBasedPRF(derivedKey, aesKeyStrength);
}
public int encryptData(byte[] buff) throws ZipException {
if (buff == null) {
throw new ZipException("input bytes are null, cannot perform AES encryption");
}
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 += AES_BLOCK_SIZE) {
loopCount = (j + AES_BLOCK_SIZE <= (start + len)) ?
AES_BLOCK_SIZE : ((start + len) - j);
prepareBuffAESIVBytes(iv, nonce);
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 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;
} else if (size == 16) {
rounds = 4;
}
byte[] salt = new byte[size];
for (int j = 0; j < rounds; j++) {
int i = random.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 byte[] getSaltBytes() {
return saltBytes;
}
}

View File

@@ -0,0 +1,99 @@
package net.lingala.zip4j.crypto;
import net.lingala.zip4j.crypto.PBKDF2.MacBasedPRF;
import net.lingala.zip4j.crypto.PBKDF2.PBKDF2Engine;
import net.lingala.zip4j.crypto.PBKDF2.PBKDF2Parameters;
import net.lingala.zip4j.crypto.engine.AESEngine;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import static net.lingala.zip4j.util.InternalZipConstants.AES_HASH_CHARSET;
import static net.lingala.zip4j.util.InternalZipConstants.AES_HASH_ITERATIONS;
import static net.lingala.zip4j.util.InternalZipConstants.AES_MAC_ALGORITHM;
import static net.lingala.zip4j.util.InternalZipConstants.AES_PASSWORD_VERIFIER_LENGTH;
public class AesCipherUtil {
private static final int START_INDEX = 0;
/**
* Derive Password-Based Key for AES according to AE-1 and AE-2 Specifications
*
* @param salt Salt used for PBKDF2
* @param password Password used for PBKDF2 containing characters matching ISO-8859-1 character set
* @param aesKeyStrength Requested AES Key and MAC Strength
* @return Derived Password-Based Key
* @throws ZipException Thrown when Derived Key is not valid
*/
public static byte[] derivePasswordBasedKey(final byte[] salt, final char[] password, final AesKeyStrength aesKeyStrength) throws ZipException {
final PBKDF2Parameters parameters = new PBKDF2Parameters(AES_MAC_ALGORITHM, AES_HASH_CHARSET, salt, AES_HASH_ITERATIONS);
final PBKDF2Engine engine = new PBKDF2Engine(parameters);
final int keyLength = aesKeyStrength.getKeyLength();
final int macLength = aesKeyStrength.getMacLength();
final int derivedKeyLength = keyLength + macLength + AES_PASSWORD_VERIFIER_LENGTH;
final byte[] derivedKey = engine.deriveKey(password, derivedKeyLength);
if (derivedKey != null && derivedKey.length == derivedKeyLength) {
return derivedKey;
} else {
final String message = String.format("Derived Key invalid for Key Length [%d] MAC Length [%d]", keyLength, macLength);
throw new ZipException(message);
}
}
/**
* Derive Password Verifier using Derived Key and requested AES Key Strength
*
* @param derivedKey Derived Key
* @param aesKeyStrength AES Key Strength
* @return Derived Password Verifier
*/
public static byte[] derivePasswordVerifier(final byte[] derivedKey, final AesKeyStrength aesKeyStrength) {
byte[] derivedPasswordVerifier = new byte[AES_PASSWORD_VERIFIER_LENGTH];
final int keyMacLength = aesKeyStrength.getKeyLength() + aesKeyStrength.getMacLength();
System.arraycopy(derivedKey, keyMacLength, derivedPasswordVerifier, START_INDEX, AES_PASSWORD_VERIFIER_LENGTH);
return derivedPasswordVerifier;
}
/**
* Get MAC-Based PRF using default HMAC Algorithm defined in AE-1 and AE-2 Specification
*
* @param derivedKey Derived Key
* @param aesKeyStrength AES Key Strength
* @return Initialized MAC-Based PRF
*/
public static MacBasedPRF getMacBasedPRF(final byte[] derivedKey, final AesKeyStrength aesKeyStrength) {
final int macLength = aesKeyStrength.getMacLength();
final byte[] macKey = new byte[macLength];
System.arraycopy(derivedKey, aesKeyStrength.getKeyLength(), macKey, START_INDEX, macLength);
final MacBasedPRF macBasedPRF = new MacBasedPRF(AES_MAC_ALGORITHM);
macBasedPRF.init(macKey);
return macBasedPRF;
}
/**
* Get AES Engine using derived key and requested AES Key Strength
*
* @param derivedKey Derived Key
* @param aesKeyStrength AES Key Strength
* @return AES Engine configured with AES Key
* @throws ZipException Thrown on AESEngine initialization failures
*/
public static AESEngine getAESEngine(final byte[] derivedKey, final AesKeyStrength aesKeyStrength) throws ZipException {
final int keyLength = aesKeyStrength.getKeyLength();
final byte[] aesKey = new byte[keyLength];
System.arraycopy(derivedKey, START_INDEX, aesKey, START_INDEX, keyLength);
return new AESEngine(aesKey);
}
public static void prepareBuffAESIVBytes(byte[] buff, int nonce) {
buff[0] = (byte) nonce;
buff[1] = (byte) (nonce >> 8);
buff[2] = (byte) (nonce >> 16);
buff[3] = (byte) (nonce >> 24);
for (int i = 4; i <= 15; i++) {
buff[i] = 0;
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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 net.lingala.zip4j.crypto;
import net.lingala.zip4j.exception.ZipException;
public interface Decrypter {
int decryptData(byte[] buff, int start, int len) throws ZipException;
}

View File

@@ -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 net.lingala.zip4j.crypto;
import net.lingala.zip4j.exception.ZipException;
public interface Encrypter {
int encryptData(byte[] buff) throws ZipException;
int encryptData(byte[] buff, int start, int len) throws ZipException;
}

View File

@@ -0,0 +1,70 @@
/*
* 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 net.lingala.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 + "'");
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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 net.lingala.zip4j.crypto.PBKDF2;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/*
* Source referred from Matthias Gartner's PKCS#5 implementation -
* see http://rtner.de/software/PBKDF2.html
*/
public class MacBasedPRF implements PRF {
private Mac mac;
private int hLen;
private 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 byte[] doFinal(byte[] M) {
return mac.doFinal(M);
}
public byte[] doFinal() {
return mac.doFinal();
}
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);
}
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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 net.lingala.zip4j.crypto.PBKDF2;
import static net.lingala.zip4j.util.Zip4jUtil.convertCharArrayToByteArray;
/*
* Source referred from Matthias Gartner's PKCS#5 implementation -
* see http://rtner.de/software/PBKDF2.html
*/
public class PBKDF2Engine {
private PBKDF2Parameters parameters;
private PRF prf;
public PBKDF2Engine(PBKDF2Parameters parameters) {
this(parameters, 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 p[];
if (inputPassword == null) {
throw new NullPointerException();
}
p = convertCharArrayToByteArray(inputPassword);
assertPRF(p);
if (dkLen == 0) {
dkLen = prf.getHLen();
}
return PBKDF2(prf, parameters.getSalt(), parameters.getIterationCount(), dkLen);
}
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;
}
private void assertPRF(byte[] P) {
if (prf == null) {
prf = new MacBasedPRF(parameters.getHashAlgorithm());
}
prf.init(P);
}
public PRF getPseudoRandomFunction() {
return prf;
}
private 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;
}
private int ceil(int a, int b) {
int m = 0;
if (a % b > 0) {
m = 1;
}
return a / b + m;
}
private 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);
}
private 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] = (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;
}
}

View File

@@ -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 net.lingala.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[] pSplit = s.split(":");
if (pSplit.length != 3) {
return true;
}
byte salt[] = BinTools.hex2bin(pSplit[0]);
int iterationCount = Integer.parseInt(pSplit[1]);
byte bDK[] = BinTools.hex2bin(pSplit[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;
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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 net.lingala.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, hashCharset, salt, iterationCount, 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;
}
}

View File

@@ -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 net.lingala.zip4j.crypto.PBKDF2;
/*
* Source referred from Matthias Gartner's PKCS#5 implementation -
* see http://rtner.de/software/PBKDF2.html
*/
interface PRF {
void init(byte[] P);
byte[] doFinal(byte[] M);
int getHLen();
}

View File

@@ -0,0 +1,82 @@
/*
* 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 net.lingala.zip4j.crypto;
import net.lingala.zip4j.crypto.engine.ZipCryptoEngine;
import net.lingala.zip4j.exception.ZipException;
import static net.lingala.zip4j.util.InternalZipConstants.STD_DEC_HDR_SIZE;
public class StandardDecrypter implements Decrypter {
private char[] password;
private byte[] crcBytes;
private byte[] crc = new byte[4];
private ZipCryptoEngine zipCryptoEngine;
public StandardDecrypter(char[] password, byte[] crcBytes , byte[] headerBytes) throws ZipException {
this.password = password;
this.crcBytes = crcBytes;
this.zipCryptoEngine = new ZipCryptoEngine();
init(headerBytes);
}
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 decrypt data");
}
for (int i = start; i < start + len; i++) {
int val = buff[i] & 0xff;
val = (val ^ zipCryptoEngine.decryptByte()) & 0xff;
zipCryptoEngine.updateKeys((byte) val);
buff[i] = (byte) val;
}
return len;
}
private void init(byte[] headerBytes) throws ZipException {
crc[3] = (byte) (crcBytes[3] & 0xFF);
crc[2] = (byte) ((crcBytes[3] >> 8) & 0xFF);
crc[1] = (byte) ((crcBytes[3] >> 16) & 0xFF);
crc[0] = (byte) ((crcBytes[3] >> 24) & 0xFF);
if (crc[2] > 0 || crc[1] > 0 || crc[0] > 0)
throw new IllegalStateException("Invalid CRC in File Header");
if (password == null || password.length <= 0) {
throw new ZipException("Wrong password!", ZipException.Type.WRONG_PASSWORD);
}
zipCryptoEngine.initKeys(password);
int result = headerBytes[0];
for (int i = 0; i < 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 != STD_DEC_HDR_SIZE)
result = headerBytes[i + 1];
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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 net.lingala.zip4j.crypto;
import net.lingala.zip4j.crypto.engine.ZipCryptoEngine;
import net.lingala.zip4j.exception.ZipException;
import java.security.SecureRandom;
import static net.lingala.zip4j.util.InternalZipConstants.STD_DEC_HDR_SIZE;
public class StandardEncrypter implements Encrypter {
private ZipCryptoEngine zipCryptoEngine;
private byte[] headerBytes;
public StandardEncrypter(char[] password, long key) throws ZipException {
this.zipCryptoEngine = new ZipCryptoEngine();
this.headerBytes = new byte[STD_DEC_HDR_SIZE];
init(password, key);
}
private void init(char[] password, long key) 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(STD_DEC_HDR_SIZE);
// Initialize again since the generated bytes were encrypted.
zipCryptoEngine.initKeys(password);
headerBytes[STD_DEC_HDR_SIZE - 1] = (byte) ((key >>> 24));
headerBytes[STD_DEC_HDR_SIZE - 2] = (byte) ((key >>> 16));
if (headerBytes.length < 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");
}
for (int i = start; i < start + len; i++) {
buff[i] = encryptByte(buff[i]);
}
return len;
}
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];
SecureRandom random = new SecureRandom();
for (int i = 0; i < buff.length; i++) {
buff[i] = encryptByte((byte) random.nextInt(256));
}
return buff;
}
public byte[] getHeaderBytes() {
return headerBytes;
}
}

View File

@@ -0,0 +1,283 @@
/*
* 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 net.lingala.zip4j.crypto.engine;
import net.lingala.zip4j.exception.ZipException;
import static net.lingala.zip4j.util.InternalZipConstants.AES_BLOCK_SIZE;
/**
* 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);
}
private 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 AES_BLOCK_SIZE;
}
private 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 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 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};
}

View File

@@ -0,0 +1,62 @@
/*
* 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 net.lingala.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 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);
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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 net.lingala.zip4j.exception;
import java.io.IOException;
public class ZipException extends IOException {
private static final long serialVersionUID = 1L;
private Type type = Type.UNKNOWN;
public ZipException(String message) {
super(message);
}
public ZipException(Exception rootException) {
super(rootException);
}
public ZipException(String message, Exception rootException) {
super(message, rootException);
}
public ZipException(String message, Type type) {
super(message);
this.type = type;
}
public ZipException(String message, Throwable throwable, Type type) {
super(message, throwable);
this.type = type;
}
public Type getType() {
return type;
}
public enum Type {
WRONG_PASSWORD,
TASK_CANCELLED_EXCEPTION,
CHECKSUM_MISMATCH,
UNKNOWN_COMPRESSION_METHOD,
FILE_NOT_FOUND,
UNSUPPORTED_ENCRYPTION,
UNKNOWN
}
}

View File

@@ -0,0 +1,176 @@
package net.lingala.zip4j.headers;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.AESExtraDataRecord;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.util.FileUtils;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.RawIO;
import net.lingala.zip4j.util.Zip4jUtil;
import java.nio.charset.Charset;
import static net.lingala.zip4j.util.BitUtils.setBit;
import static net.lingala.zip4j.util.BitUtils.unsetBit;
import static net.lingala.zip4j.util.FileUtils.isZipEntryDirectory;
import static net.lingala.zip4j.util.ZipVersionUtils.determineVersionMadeBy;
import static net.lingala.zip4j.util.ZipVersionUtils.determineVersionNeededToExtract;
public class FileHeaderFactory {
public FileHeader generateFileHeader(ZipParameters zipParameters, boolean isSplitZip, int currentDiskNumberStart,
Charset charset, RawIO rawIO)
throws ZipException {
FileHeader fileHeader = new FileHeader();
fileHeader.setSignature(HeaderSignature.CENTRAL_DIRECTORY);
fileHeader.setVersionMadeBy(determineVersionMadeBy(zipParameters, rawIO));
fileHeader.setVersionNeededToExtract(determineVersionNeededToExtract(zipParameters).getCode());
if (zipParameters.isEncryptFiles() && zipParameters.getEncryptionMethod() == EncryptionMethod.AES) {
fileHeader.setCompressionMethod(CompressionMethod.AES_INTERNAL_ONLY);
fileHeader.setAesExtraDataRecord(generateAESExtraDataRecord(zipParameters));
fileHeader.setExtraFieldLength(fileHeader.getExtraFieldLength() + InternalZipConstants.AES_EXTRA_DATA_RECORD_SIZE);
} else {
fileHeader.setCompressionMethod(zipParameters.getCompressionMethod());
}
if (zipParameters.isEncryptFiles()) {
if (zipParameters.getEncryptionMethod() == null || zipParameters.getEncryptionMethod() == EncryptionMethod.NONE) {
throw new ZipException("Encryption method has to be set when encryptFiles flag is set in zip parameters");
}
fileHeader.setEncrypted(true);
fileHeader.setEncryptionMethod(zipParameters.getEncryptionMethod());
}
String fileName = validateAndGetFileName(zipParameters.getFileNameInZip());
fileHeader.setFileName(fileName);
fileHeader.setFileNameLength(determineFileNameLength(fileName, charset));
fileHeader.setDiskNumberStart(isSplitZip ? currentDiskNumberStart : 0);
if (zipParameters.getLastModifiedFileTime() > 0) {
fileHeader.setLastModifiedTime(Zip4jUtil.epochToExtendedDosTime(zipParameters.getLastModifiedFileTime()));
} else {
fileHeader.setLastModifiedTime(Zip4jUtil.epochToExtendedDosTime(System.currentTimeMillis()));
}
boolean isDirectory = isZipEntryDirectory(fileName);
fileHeader.setDirectory(isDirectory);
fileHeader.setExternalFileAttributes(FileUtils.getDefaultFileAttributes(isDirectory));
if (zipParameters.isWriteExtendedLocalFileHeader() && zipParameters.getEntrySize() == -1) {
fileHeader.setUncompressedSize(0);
} else {
fileHeader.setUncompressedSize(zipParameters.getEntrySize());
}
if (zipParameters.isEncryptFiles() && zipParameters.getEncryptionMethod() == EncryptionMethod.ZIP_STANDARD) {
fileHeader.setCrc(zipParameters.getEntryCRC());
}
fileHeader.setGeneralPurposeFlag(determineGeneralPurposeBitFlag(fileHeader.isEncrypted(), zipParameters, charset));
fileHeader.setDataDescriptorExists(zipParameters.isWriteExtendedLocalFileHeader());
fileHeader.setFileComment(zipParameters.getFileComment());
return fileHeader;
}
public LocalFileHeader generateLocalFileHeader(FileHeader fileHeader) {
LocalFileHeader localFileHeader = new LocalFileHeader();
localFileHeader.setSignature(HeaderSignature.LOCAL_FILE_HEADER);
localFileHeader.setVersionNeededToExtract(fileHeader.getVersionNeededToExtract());
localFileHeader.setCompressionMethod(fileHeader.getCompressionMethod());
localFileHeader.setLastModifiedTime(fileHeader.getLastModifiedTime());
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.setCrc(fileHeader.getCrc());
localFileHeader.setCompressedSize(fileHeader.getCompressedSize());
localFileHeader.setGeneralPurposeFlag(fileHeader.getGeneralPurposeFlag().clone());
localFileHeader.setDataDescriptorExists(fileHeader.isDataDescriptorExists());
localFileHeader.setExtraFieldLength(fileHeader.getExtraFieldLength());
return localFileHeader;
}
private byte[] determineGeneralPurposeBitFlag(boolean isEncrypted, ZipParameters zipParameters, Charset charset) {
byte[] generalPurposeBitFlag = new byte[2];
generalPurposeBitFlag[0] = generateFirstGeneralPurposeByte(isEncrypted, zipParameters);
if(charset.equals(InternalZipConstants.CHARSET_UTF_8)) {
generalPurposeBitFlag[1] = setBit(generalPurposeBitFlag[1], 3); // set 3rd bit which corresponds to utf-8 file name charset
}
return generalPurposeBitFlag;
}
private byte generateFirstGeneralPurposeByte(boolean isEncrypted, ZipParameters zipParameters) {
byte firstByte = 0;
if (isEncrypted) {
firstByte = setBit(firstByte, 0);
}
if (CompressionMethod.DEFLATE.equals(zipParameters.getCompressionMethod())) {
if (CompressionLevel.NORMAL.equals(zipParameters.getCompressionLevel())) {
firstByte = unsetBit(firstByte, 1);
firstByte = unsetBit(firstByte, 2);
} else if (CompressionLevel.MAXIMUM.equals(zipParameters.getCompressionLevel())) {
firstByte = setBit(firstByte, 1);
firstByte = unsetBit(firstByte, 2);
} else if (CompressionLevel.FAST.equals(zipParameters.getCompressionLevel())) {
firstByte = unsetBit(firstByte, 1);
firstByte = setBit(firstByte, 2);
} else if (CompressionLevel.FASTEST.equals(zipParameters.getCompressionLevel())
|| CompressionLevel.ULTRA.equals(zipParameters.getCompressionLevel())) {
firstByte = setBit(firstByte, 1);
firstByte = setBit(firstByte, 2);
}
}
if (zipParameters.isWriteExtendedLocalFileHeader()) {
firstByte = setBit(firstByte, 3);
}
return firstByte;
}
private String validateAndGetFileName(String fileNameInZip) throws ZipException {
if (!Zip4jUtil.isStringNotNullAndNotEmpty(fileNameInZip)) {
throw new ZipException("fileNameInZip is null or empty");
}
return fileNameInZip;
}
private AESExtraDataRecord generateAESExtraDataRecord(ZipParameters parameters) throws ZipException {
AESExtraDataRecord aesExtraDataRecord = new AESExtraDataRecord();
if (parameters.getAesVersion() != null) {
aesExtraDataRecord.setAesVersion(parameters.getAesVersion());
}
if (parameters.getAesKeyStrength() == AesKeyStrength.KEY_STRENGTH_128) {
aesExtraDataRecord.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_128);
} else if (parameters.getAesKeyStrength() == AesKeyStrength.KEY_STRENGTH_192) {
aesExtraDataRecord.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_192);
} else if (parameters.getAesKeyStrength() == AesKeyStrength.KEY_STRENGTH_256) {
aesExtraDataRecord.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
} else {
throw new ZipException("invalid AES key strength");
}
aesExtraDataRecord.setCompressionMethod(parameters.getCompressionMethod());
return aesExtraDataRecord;
}
private int determineFileNameLength(String fileName, Charset charset) {
return fileName.getBytes(charset).length;
}
}

View File

@@ -0,0 +1,753 @@
/*
* 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 net.lingala.zip4j.headers;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.io.inputstream.NumberedSplitRandomAccessFile;
import net.lingala.zip4j.model.AESExtraDataRecord;
import net.lingala.zip4j.model.CentralDirectory;
import net.lingala.zip4j.model.DataDescriptor;
import net.lingala.zip4j.model.DigitalSignature;
import net.lingala.zip4j.model.EndOfCentralDirectoryRecord;
import net.lingala.zip4j.model.ExtraDataRecord;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.Zip64EndOfCentralDirectoryLocator;
import net.lingala.zip4j.model.Zip64EndOfCentralDirectoryRecord;
import net.lingala.zip4j.model.Zip64ExtendedInfo;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import net.lingala.zip4j.model.enums.AesVersion;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.util.RawIO;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static net.lingala.zip4j.headers.HeaderUtil.decodeStringWithCharset;
import static net.lingala.zip4j.util.BitUtils.isBitSet;
import static net.lingala.zip4j.util.InternalZipConstants.BUFF_SIZE;
import static net.lingala.zip4j.util.InternalZipConstants.ENDHDR;
import static net.lingala.zip4j.util.InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT;
import static net.lingala.zip4j.util.InternalZipConstants.ZIP_64_SIZE_LIMIT;
import static net.lingala.zip4j.util.Zip4jUtil.readFully;
/**
* Helper class to read header information for the zip file
*/
public class HeaderReader {
private ZipModel zipModel;
private RawIO rawIO = new RawIO();
private byte[] intBuff = new byte[4];
public ZipModel readAllHeaders(RandomAccessFile zip4jRaf, Charset charset) throws IOException {
if (zip4jRaf.length() < ENDHDR) {
throw new ZipException("Zip file size less than minimum expected zip file size. " +
"Probably not a zip file or a corrupted zip file");
}
zipModel = new ZipModel();
try {
zipModel.setEndOfCentralDirectoryRecord(readEndOfCentralDirectoryRecord(zip4jRaf, rawIO, charset));
} catch (ZipException e){
throw e;
} catch (IOException e) {
throw new ZipException("Zip headers not found. Probably not a zip file or a corrupted zip file", e);
}
if (zipModel.getEndOfCentralDirectoryRecord().getTotalNumberOfEntriesInCentralDirectory() == 0) {
return zipModel;
}
// If file is Zip64 format, Zip64 headers have to be read before reading central directory
zipModel.setZip64EndOfCentralDirectoryLocator(readZip64EndOfCentralDirectoryLocator(zip4jRaf, rawIO,
zipModel.getEndOfCentralDirectoryRecord().getOffsetOfEndOfCentralDirectory()));
if (zipModel.isZip64Format()) {
zipModel.setZip64EndOfCentralDirectoryRecord(readZip64EndCentralDirRec(zip4jRaf, rawIO));
if (zipModel.getZip64EndOfCentralDirectoryRecord() != null
&& zipModel.getZip64EndOfCentralDirectoryRecord().getNumberOfThisDisk() > 0) {
zipModel.setSplitArchive(true);
} else {
zipModel.setSplitArchive(false);
}
}
zipModel.setCentralDirectory(readCentralDirectory(zip4jRaf, rawIO, charset));
return zipModel;
}
private EndOfCentralDirectoryRecord readEndOfCentralDirectoryRecord(RandomAccessFile zip4jRaf, RawIO rawIO, Charset charset)
throws IOException {
long offsetEndOfCentralDirectory = zip4jRaf.length() - ENDHDR;
seekInCurrentPart(zip4jRaf, offsetEndOfCentralDirectory);
int headerSignature = rawIO.readIntLittleEndian(zip4jRaf);
if (headerSignature != HeaderSignature.END_OF_CENTRAL_DIRECTORY.getValue()) {
offsetEndOfCentralDirectory = determineOffsetOfEndOfCentralDirectory(zip4jRaf);
zip4jRaf.seek(offsetEndOfCentralDirectory + 4); // 4 to ignore reading signature again
}
EndOfCentralDirectoryRecord endOfCentralDirectoryRecord = new EndOfCentralDirectoryRecord();
endOfCentralDirectoryRecord.setSignature(HeaderSignature.END_OF_CENTRAL_DIRECTORY);
endOfCentralDirectoryRecord.setNumberOfThisDisk(rawIO.readShortLittleEndian(zip4jRaf));
endOfCentralDirectoryRecord.setNumberOfThisDiskStartOfCentralDir(rawIO.readShortLittleEndian(zip4jRaf));
endOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(
rawIO.readShortLittleEndian(zip4jRaf));
endOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectory(rawIO.readShortLittleEndian(zip4jRaf));
endOfCentralDirectoryRecord.setSizeOfCentralDirectory(rawIO.readIntLittleEndian(zip4jRaf));
endOfCentralDirectoryRecord.setOffsetOfEndOfCentralDirectory(offsetEndOfCentralDirectory);
zip4jRaf.readFully(intBuff);
endOfCentralDirectoryRecord.setOffsetOfStartOfCentralDirectory(rawIO.readLongLittleEndian(intBuff, 0));
int commentLength = rawIO.readShortLittleEndian(zip4jRaf);
endOfCentralDirectoryRecord.setComment(readZipComment(zip4jRaf, commentLength, charset));
zipModel.setSplitArchive(endOfCentralDirectoryRecord.getNumberOfThisDisk() > 0);
return endOfCentralDirectoryRecord;
}
private CentralDirectory readCentralDirectory(RandomAccessFile zip4jRaf, RawIO rawIO, Charset charset) throws IOException {
CentralDirectory centralDirectory = new CentralDirectory();
List<FileHeader> fileHeaders = new ArrayList<>();
long offSetStartCentralDir = HeaderUtil.getOffsetStartOfCentralDirectory(zipModel);
long centralDirEntryCount = getNumberOfEntriesInCentralDirectory(zipModel);
zip4jRaf.seek(offSetStartCentralDir);
byte[] shortBuff = new byte[2];
byte[] intBuff = new byte[4];
for (int i = 0; i < centralDirEntryCount; i++) {
FileHeader fileHeader = new FileHeader();
if (rawIO.readIntLittleEndian(zip4jRaf) != HeaderSignature.CENTRAL_DIRECTORY.getValue()) {
throw new ZipException("Expected central directory entry not found (#" + (i + 1) + ")");
}
fileHeader.setSignature(HeaderSignature.CENTRAL_DIRECTORY);
fileHeader.setVersionMadeBy(rawIO.readShortLittleEndian(zip4jRaf));
fileHeader.setVersionNeededToExtract(rawIO.readShortLittleEndian(zip4jRaf));
byte[] generalPurposeFlags = new byte[2];
zip4jRaf.readFully(generalPurposeFlags);
fileHeader.setEncrypted(isBitSet(generalPurposeFlags[0], 0));
fileHeader.setDataDescriptorExists(isBitSet(generalPurposeFlags[0], 3));
fileHeader.setFileNameUTF8Encoded(isBitSet(generalPurposeFlags[1], 3));
fileHeader.setGeneralPurposeFlag(generalPurposeFlags.clone());
fileHeader.setCompressionMethod(CompressionMethod.getCompressionMethodFromCode(rawIO.readShortLittleEndian(
zip4jRaf)));
fileHeader.setLastModifiedTime(rawIO.readIntLittleEndian(zip4jRaf));
zip4jRaf.readFully(intBuff);
fileHeader.setCrc(rawIO.readLongLittleEndian(intBuff, 0));
fileHeader.setCrcRawData(intBuff);
fileHeader.setCompressedSize(rawIO.readLongLittleEndian(zip4jRaf, 4));
fileHeader.setUncompressedSize(rawIO.readLongLittleEndian(zip4jRaf, 4));
int fileNameLength = rawIO.readShortLittleEndian(zip4jRaf);
fileHeader.setFileNameLength(fileNameLength);
fileHeader.setExtraFieldLength(rawIO.readShortLittleEndian(zip4jRaf));
int fileCommentLength = rawIO.readShortLittleEndian(zip4jRaf);
fileHeader.setFileCommentLength(fileCommentLength);
fileHeader.setDiskNumberStart(rawIO.readShortLittleEndian(zip4jRaf));
zip4jRaf.readFully(shortBuff);
fileHeader.setInternalFileAttributes(shortBuff.clone());
zip4jRaf.readFully(intBuff);
fileHeader.setExternalFileAttributes(intBuff.clone());
zip4jRaf.readFully(intBuff);
fileHeader.setOffsetLocalHeader(rawIO.readLongLittleEndian(intBuff, 0));
if (fileNameLength > 0) {
byte[] fileNameBuff = new byte[fileNameLength];
zip4jRaf.readFully(fileNameBuff);
String fileName = decodeStringWithCharset(fileNameBuff, fileHeader.isFileNameUTF8Encoded(), charset);
if (fileName.contains(":\\")) {
fileName = fileName.substring(fileName.indexOf(":\\") + 2);
}
fileHeader.setFileName(fileName);
fileHeader.setDirectory(fileName.endsWith("/") || fileName.endsWith("\\"));
} else {
fileHeader.setFileName(null);
}
readExtraDataRecords(zip4jRaf, fileHeader);
readZip64ExtendedInfo(fileHeader, rawIO);
readAesExtraDataRecord(fileHeader, rawIO);
if (fileCommentLength > 0) {
byte[] fileCommentBuff = new byte[fileCommentLength];
zip4jRaf.readFully(fileCommentBuff);
fileHeader.setFileComment(decodeStringWithCharset(fileCommentBuff, fileHeader.isFileNameUTF8Encoded(), charset));
}
if (fileHeader.isEncrypted()) {
if (fileHeader.getAesExtraDataRecord() != null) {
fileHeader.setEncryptionMethod(EncryptionMethod.AES);
} else {
fileHeader.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
}
}
fileHeaders.add(fileHeader);
}
centralDirectory.setFileHeaders(fileHeaders);
DigitalSignature digitalSignature = new DigitalSignature();
if (rawIO.readIntLittleEndian(zip4jRaf) == HeaderSignature.DIGITAL_SIGNATURE.getValue()) {
digitalSignature.setSignature(HeaderSignature.DIGITAL_SIGNATURE);
digitalSignature.setSizeOfData(rawIO.readShortLittleEndian(zip4jRaf));
if (digitalSignature.getSizeOfData() > 0) {
byte[] signatureDataBuff = new byte[digitalSignature.getSizeOfData()];
zip4jRaf.readFully(signatureDataBuff);
digitalSignature.setSignatureData(new String(signatureDataBuff));
}
}
return centralDirectory;
}
private void readExtraDataRecords(RandomAccessFile zip4jRaf, FileHeader fileHeader)
throws IOException {
int extraFieldLength = fileHeader.getExtraFieldLength();
if (extraFieldLength <= 0) {
return;
}
fileHeader.setExtraDataRecords(readExtraDataRecords(zip4jRaf, extraFieldLength));
}
private void readExtraDataRecords(InputStream inputStream, LocalFileHeader localFileHeader)
throws IOException {
int extraFieldLength = localFileHeader.getExtraFieldLength();
if (extraFieldLength <= 0) {
return;
}
localFileHeader.setExtraDataRecords(readExtraDataRecords(inputStream, extraFieldLength));
}
private List<ExtraDataRecord> readExtraDataRecords(RandomAccessFile zip4jRaf, int extraFieldLength)
throws IOException {
if (extraFieldLength < 4) {
if (extraFieldLength > 0) {
zip4jRaf.skipBytes(extraFieldLength);
}
return null;
}
byte[] extraFieldBuf = new byte[extraFieldLength];
zip4jRaf.read(extraFieldBuf);
try {
return parseExtraDataRecords(extraFieldBuf, extraFieldLength);
} catch (Exception e) {
// Ignore any errors when parsing extra data records
return Collections.emptyList();
}
}
private List<ExtraDataRecord> readExtraDataRecords(InputStream inputStream, int extraFieldLength)
throws IOException {
if (extraFieldLength < 4) {
if (extraFieldLength > 0) {
inputStream.skip(extraFieldLength);
}
return null;
}
byte[] extraFieldBuf = new byte[extraFieldLength];
readFully(inputStream, extraFieldBuf);
try {
return parseExtraDataRecords(extraFieldBuf, extraFieldLength);
} catch (Exception e) {
// Ignore any errors when parsing extra data records
return Collections.emptyList();
}
}
private List<ExtraDataRecord> parseExtraDataRecords(byte[] extraFieldBuf, int extraFieldLength) {
int counter = 0;
List<ExtraDataRecord> extraDataRecords = new ArrayList<>();
while (counter < extraFieldLength) {
ExtraDataRecord extraDataRecord = new ExtraDataRecord();
int header = rawIO.readShortLittleEndian(extraFieldBuf, counter);
extraDataRecord.setHeader(header);
counter += 2;
int sizeOfRec = rawIO.readShortLittleEndian(extraFieldBuf, counter);
extraDataRecord.setSizeOfData(sizeOfRec);
counter += 2;
if (sizeOfRec > 0) {
byte[] data = new byte[sizeOfRec];
System.arraycopy(extraFieldBuf, counter, data, 0, sizeOfRec);
extraDataRecord.setData(data);
}
counter += sizeOfRec;
extraDataRecords.add(extraDataRecord);
}
return extraDataRecords.size() > 0 ? extraDataRecords : null;
}
private Zip64EndOfCentralDirectoryLocator readZip64EndOfCentralDirectoryLocator(RandomAccessFile zip4jRaf,
RawIO rawIO, long offsetEndOfCentralDirectoryRecord) throws IOException {
Zip64EndOfCentralDirectoryLocator zip64EndOfCentralDirectoryLocator = new Zip64EndOfCentralDirectoryLocator();
setFilePointerToReadZip64EndCentralDirLoc(zip4jRaf, offsetEndOfCentralDirectoryRecord);
int signature = rawIO.readIntLittleEndian(zip4jRaf);
if (signature == HeaderSignature.ZIP64_END_CENTRAL_DIRECTORY_LOCATOR.getValue()) {
zipModel.setZip64Format(true);
zip64EndOfCentralDirectoryLocator.setSignature(HeaderSignature.ZIP64_END_CENTRAL_DIRECTORY_LOCATOR);
} else {
zipModel.setZip64Format(false);
return null;
}
zip64EndOfCentralDirectoryLocator.setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(
rawIO.readIntLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryLocator.setOffsetZip64EndOfCentralDirectoryRecord(
rawIO.readLongLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryLocator.setTotalNumberOfDiscs(rawIO.readIntLittleEndian(zip4jRaf));
return zip64EndOfCentralDirectoryLocator;
}
private Zip64EndOfCentralDirectoryRecord readZip64EndCentralDirRec(RandomAccessFile zip4jRaf, RawIO rawIO)
throws IOException {
if (zipModel.getZip64EndOfCentralDirectoryLocator() == null) {
throw new ZipException("invalid zip64 end of central directory locator");
}
long offSetStartOfZip64CentralDir = zipModel.getZip64EndOfCentralDirectoryLocator()
.getOffsetZip64EndOfCentralDirectoryRecord();
if (offSetStartOfZip64CentralDir < 0) {
throw new ZipException("invalid offset for start of end of central directory record");
}
zip4jRaf.seek(offSetStartOfZip64CentralDir);
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectoryRecord = new Zip64EndOfCentralDirectoryRecord();
int signature = rawIO.readIntLittleEndian(zip4jRaf);
if (signature != HeaderSignature.ZIP64_END_CENTRAL_DIRECTORY_RECORD.getValue()) {
throw new ZipException("invalid signature for zip64 end of central directory record");
}
zip64EndOfCentralDirectoryRecord.setSignature(HeaderSignature.ZIP64_END_CENTRAL_DIRECTORY_RECORD);
zip64EndOfCentralDirectoryRecord.setSizeOfZip64EndCentralDirectoryRecord(rawIO.readLongLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryRecord.setVersionMadeBy(rawIO.readShortLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryRecord.setVersionNeededToExtract(rawIO.readShortLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryRecord.setNumberOfThisDisk(rawIO.readIntLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryRecord.setNumberOfThisDiskStartOfCentralDirectory(rawIO.readIntLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(
rawIO.readLongLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectory(rawIO.readLongLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryRecord.setSizeOfCentralDirectory(rawIO.readLongLittleEndian(zip4jRaf));
zip64EndOfCentralDirectoryRecord.setOffsetStartCentralDirectoryWRTStartDiskNumber(
rawIO.readLongLittleEndian(zip4jRaf));
//zip64 extensible data sector
//44 is the size of fixed variables in this record
long extDataSecSize = zip64EndOfCentralDirectoryRecord.getSizeOfZip64EndCentralDirectoryRecord() - 44;
if (extDataSecSize > 0) {
byte[] extDataSecRecBuf = new byte[(int) extDataSecSize];
zip4jRaf.readFully(extDataSecRecBuf);
zip64EndOfCentralDirectoryRecord.setExtensibleDataSector(extDataSecRecBuf);
}
return zip64EndOfCentralDirectoryRecord;
}
private void readZip64ExtendedInfo(FileHeader fileHeader, RawIO rawIO) throws ZipException {
if (fileHeader.getExtraDataRecords() == null || fileHeader.getExtraDataRecords().size() <= 0) {
return;
}
Zip64ExtendedInfo zip64ExtendedInfo = readZip64ExtendedInfo(fileHeader.getExtraDataRecords(), rawIO,
fileHeader.getUncompressedSize(), fileHeader.getCompressedSize(), fileHeader.getOffsetLocalHeader(),
fileHeader.getDiskNumberStart());
if (zip64ExtendedInfo == null) {
return;
}
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());
}
}
private void readZip64ExtendedInfo(LocalFileHeader localFileHeader, RawIO rawIO) 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(), rawIO,
localFileHeader.getUncompressedSize(), localFileHeader.getCompressedSize(), 0, 0);
if (zip64ExtendedInfo == null) {
return;
}
localFileHeader.setZip64ExtendedInfo(zip64ExtendedInfo);
if (zip64ExtendedInfo.getUncompressedSize() != -1) {
localFileHeader.setUncompressedSize(zip64ExtendedInfo.getUncompressedSize());
}
if (zip64ExtendedInfo.getCompressedSize() != -1) {
localFileHeader.setCompressedSize(zip64ExtendedInfo.getCompressedSize());
}
}
private Zip64ExtendedInfo readZip64ExtendedInfo(List<ExtraDataRecord> extraDataRecords, RawIO rawIO,
long uncompressedSize, long compressedSize, long offsetLocalHeader,
int diskNumberStart) {
for (ExtraDataRecord extraDataRecord : extraDataRecords) {
if (extraDataRecord == null) {
continue;
}
if (HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue() == extraDataRecord.getHeader()) {
Zip64ExtendedInfo zip64ExtendedInfo = new Zip64ExtendedInfo();
byte[] extraData = extraDataRecord.getData();
if (extraDataRecord.getSizeOfData() <= 0) {
return null;
}
int counter = 0;
if (counter < extraDataRecord.getSizeOfData() && uncompressedSize == ZIP_64_SIZE_LIMIT) {
zip64ExtendedInfo.setUncompressedSize(rawIO.readLongLittleEndian(extraData, counter));
counter += 8;
}
if (counter < extraDataRecord.getSizeOfData() && compressedSize == ZIP_64_SIZE_LIMIT) {
zip64ExtendedInfo.setCompressedSize(rawIO.readLongLittleEndian(extraData, counter));
counter += 8;
}
if (counter < extraDataRecord.getSizeOfData() && offsetLocalHeader == ZIP_64_SIZE_LIMIT) {
zip64ExtendedInfo.setOffsetLocalHeader(rawIO.readLongLittleEndian(extraData, counter));
counter += 8;
}
if (counter < extraDataRecord.getSizeOfData() && diskNumberStart == ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
zip64ExtendedInfo.setDiskNumberStart(rawIO.readIntLittleEndian(extraData, counter));
}
return zip64ExtendedInfo;
}
}
return null;
}
private void setFilePointerToReadZip64EndCentralDirLoc(RandomAccessFile zip4jRaf,
long offsetEndOfCentralDirectoryRecord) throws IOException {
// Now the file pointer is at the end of signature of Central Dir Rec
// Seek back with the following values
// 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
seekInCurrentPart(zip4jRaf, offsetEndOfCentralDirectoryRecord - 4 - 8 - 4 - 4);
}
public LocalFileHeader readLocalFileHeader(InputStream inputStream, Charset charset) throws IOException {
LocalFileHeader localFileHeader = new LocalFileHeader();
byte[] intBuff = new byte[4];
//signature
int sig = rawIO.readIntLittleEndian(inputStream);
if (sig != HeaderSignature.LOCAL_FILE_HEADER.getValue()) {
return null;
}
localFileHeader.setSignature(HeaderSignature.LOCAL_FILE_HEADER);
localFileHeader.setVersionNeededToExtract(rawIO.readShortLittleEndian(inputStream));
byte[] generalPurposeFlags = new byte[2];
if (readFully(inputStream, generalPurposeFlags) != 2) {
throw new ZipException("Could not read enough bytes for generalPurposeFlags");
}
localFileHeader.setEncrypted(isBitSet(generalPurposeFlags[0], 0));
localFileHeader.setDataDescriptorExists(isBitSet(generalPurposeFlags[0], 3));
localFileHeader.setFileNameUTF8Encoded(isBitSet(generalPurposeFlags[1], 3));
localFileHeader.setGeneralPurposeFlag(generalPurposeFlags.clone());
localFileHeader.setCompressionMethod(CompressionMethod.getCompressionMethodFromCode(
rawIO.readShortLittleEndian(inputStream)));
localFileHeader.setLastModifiedTime(rawIO.readIntLittleEndian(inputStream));
readFully(inputStream, intBuff);
localFileHeader.setCrc(rawIO.readLongLittleEndian(intBuff, 0));
localFileHeader.setCrcRawData(intBuff.clone());
localFileHeader.setCompressedSize(rawIO.readLongLittleEndian(inputStream, 4));
localFileHeader.setUncompressedSize(rawIO.readLongLittleEndian(inputStream, 4));
int fileNameLength = rawIO.readShortLittleEndian(inputStream);
localFileHeader.setFileNameLength(fileNameLength);
localFileHeader.setExtraFieldLength(rawIO.readShortLittleEndian(inputStream));
if (fileNameLength > 0) {
byte[] fileNameBuf = new byte[fileNameLength];
readFully(inputStream, 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 = decodeStringWithCharset(fileNameBuf, localFileHeader.isFileNameUTF8Encoded(), charset);
if (fileName == null) {
throw new ZipException("file name is null, cannot assign file name to local file header");
}
if (fileName.contains(":" + System.getProperty("file.separator"))) {
fileName = fileName.substring(fileName.indexOf(":" + System.getProperty("file.separator")) + 2);
}
localFileHeader.setFileName(fileName);
localFileHeader.setDirectory(fileName.endsWith("/") || fileName.endsWith("\\"));
} else {
localFileHeader.setFileName(null);
}
readExtraDataRecords(inputStream, localFileHeader);
readZip64ExtendedInfo(localFileHeader, rawIO);
readAesExtraDataRecord(localFileHeader, rawIO);
if (localFileHeader.isEncrypted()) {
if (localFileHeader.getEncryptionMethod() == EncryptionMethod.AES) {
//Do nothing
} else {
if (BigInteger.valueOf(localFileHeader.getGeneralPurposeFlag()[0]).testBit(6)) {
localFileHeader.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD_VARIANT_STRONG);
} else {
localFileHeader.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
}
}
}
return localFileHeader;
}
public DataDescriptor readDataDescriptor(InputStream inputStream, boolean isZip64Format) throws IOException {
DataDescriptor dataDescriptor = new DataDescriptor();
byte[] intBuff = new byte[4];
readFully(inputStream, intBuff);
long sigOrCrc = rawIO.readLongLittleEndian(intBuff, 0);
//According to zip specification, presence of extra data record header signature is optional.
//If this signature is present, read it and read the next 4 bytes for crc
//If signature not present, assign the read 4 bytes for crc
if (sigOrCrc == HeaderSignature.EXTRA_DATA_RECORD.getValue()) {
dataDescriptor.setSignature(HeaderSignature.EXTRA_DATA_RECORD);
readFully(inputStream, intBuff);
dataDescriptor.setCrc(rawIO.readLongLittleEndian(intBuff, 0));
} else {
dataDescriptor.setCrc(sigOrCrc);
}
if (isZip64Format) {
dataDescriptor.setCompressedSize(rawIO.readLongLittleEndian(inputStream));
dataDescriptor.setUncompressedSize(rawIO.readLongLittleEndian(inputStream));
} else {
dataDescriptor.setCompressedSize(rawIO.readIntLittleEndian(inputStream));
dataDescriptor.setUncompressedSize(rawIO.readIntLittleEndian(inputStream));
}
return dataDescriptor;
}
private void readAesExtraDataRecord(FileHeader fileHeader, RawIO rawIO) throws ZipException {
if (fileHeader.getExtraDataRecords() == null || fileHeader.getExtraDataRecords().size() <= 0) {
return;
}
AESExtraDataRecord aesExtraDataRecord = readAesExtraDataRecord(fileHeader.getExtraDataRecords(), rawIO);
if (aesExtraDataRecord != null) {
fileHeader.setAesExtraDataRecord(aesExtraDataRecord);
fileHeader.setEncryptionMethod(EncryptionMethod.AES);
}
}
private void readAesExtraDataRecord(LocalFileHeader localFileHeader, RawIO rawIO) throws ZipException {
if (localFileHeader.getExtraDataRecords() == null || localFileHeader.getExtraDataRecords().size() <= 0) {
return;
}
AESExtraDataRecord aesExtraDataRecord = readAesExtraDataRecord(localFileHeader.getExtraDataRecords(), rawIO);
if (aesExtraDataRecord != null) {
localFileHeader.setAesExtraDataRecord(aesExtraDataRecord);
localFileHeader.setEncryptionMethod(EncryptionMethod.AES);
}
}
private AESExtraDataRecord readAesExtraDataRecord(List<ExtraDataRecord> extraDataRecords, RawIO rawIO)
throws ZipException {
if (extraDataRecords == null) {
return null;
}
for (ExtraDataRecord extraDataRecord : extraDataRecords) {
if (extraDataRecord == null) {
continue;
}
if (extraDataRecord.getHeader() == HeaderSignature.AES_EXTRA_DATA_RECORD.getValue()) {
if (extraDataRecord.getData() == null) {
throw new ZipException("corrupt AES extra data records");
}
AESExtraDataRecord aesExtraDataRecord = new AESExtraDataRecord();
aesExtraDataRecord.setSignature(HeaderSignature.AES_EXTRA_DATA_RECORD);
aesExtraDataRecord.setDataSize(extraDataRecord.getSizeOfData());
byte[] aesData = extraDataRecord.getData();
aesExtraDataRecord.setAesVersion(AesVersion.getFromVersionNumber(rawIO.readShortLittleEndian(aesData, 0)));
byte[] vendorIDBytes = new byte[2];
System.arraycopy(aesData, 2, vendorIDBytes, 0, 2);
aesExtraDataRecord.setVendorID(new String(vendorIDBytes));
aesExtraDataRecord.setAesKeyStrength(AesKeyStrength.getAesKeyStrengthFromRawCode(aesData[4] & 0xFF));
aesExtraDataRecord.setCompressionMethod(
CompressionMethod.getCompressionMethodFromCode(rawIO.readShortLittleEndian(aesData, 5)));
return aesExtraDataRecord;
}
}
return null;
}
private long getNumberOfEntriesInCentralDirectory(ZipModel zipModel) {
if (zipModel.isZip64Format()) {
return zipModel.getZip64EndOfCentralDirectoryRecord().getTotalNumberOfEntriesInCentralDirectory();
}
return zipModel.getEndOfCentralDirectoryRecord().getTotalNumberOfEntriesInCentralDirectory();
}
private long determineOffsetOfEndOfCentralDirectory(RandomAccessFile randomAccessFile) throws IOException {
byte[] buff = new byte[BUFF_SIZE];
long currentFilePointer = randomAccessFile.getFilePointer();
do {
int toRead = currentFilePointer > BUFF_SIZE ? BUFF_SIZE : (int) currentFilePointer;
// read 4 bytes again to make sure that the header is not spilled over
long seekPosition = currentFilePointer - toRead + 4;
if (seekPosition == 4) {
seekPosition = 0;
}
seekInCurrentPart(randomAccessFile, seekPosition);
randomAccessFile.read(buff, 0, toRead);
currentFilePointer = seekPosition;
for (int i = 0; i < toRead - 3; i++) {
if (rawIO.readIntLittleEndian(buff, i) == HeaderSignature.END_OF_CENTRAL_DIRECTORY.getValue()) {
return currentFilePointer + i;
}
}
} while (currentFilePointer > 0);
throw new ZipException("Zip headers not found. Probably not a zip file");
}
private void seekInCurrentPart(RandomAccessFile randomAccessFile, long pos) throws IOException {
if (randomAccessFile instanceof NumberedSplitRandomAccessFile) {
((NumberedSplitRandomAccessFile) randomAccessFile).seekInCurrentPart(pos);
} else {
randomAccessFile.seek(pos);
}
}
private String readZipComment(RandomAccessFile raf, int commentLength, Charset charset) {
if (commentLength <= 0) {
return null;
}
try {
byte[] commentBuf = new byte[commentLength];
raf.readFully(commentBuf);
return new String(commentBuf, charset);
} catch (IOException e) {
// Ignore any exception and set comment to null if comment cannot be read
return null;
}
}
}

View File

@@ -0,0 +1,26 @@
package net.lingala.zip4j.headers;
public enum HeaderSignature {
LOCAL_FILE_HEADER(0x04034b50L), // "PK\003\004"
EXTRA_DATA_RECORD(0x08074b50L), // "PK\007\008"
CENTRAL_DIRECTORY(0x02014b50L), // "PK\001\002"
END_OF_CENTRAL_DIRECTORY(0x06054b50L), // "PK\005\006"
DIGITAL_SIGNATURE(0x05054b50L),
ARCEXTDATREC(0x08064b50L),
SPLIT_ZIP(0x08074b50L),
ZIP64_END_CENTRAL_DIRECTORY_LOCATOR(0x07064b50L),
ZIP64_END_CENTRAL_DIRECTORY_RECORD(0x06064b50),
ZIP64_EXTRA_FIELD_SIGNATURE(0x0001),
AES_EXTRA_DATA_RECORD(0x9901);
private long value;
HeaderSignature(long value) {
this.value = value;
}
public long getValue() {
return value;
}
}

View File

@@ -0,0 +1,118 @@
package net.lingala.zip4j.headers;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.util.InternalZipConstants;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static net.lingala.zip4j.util.InternalZipConstants.ZIP_STANDARD_CHARSET;
import static net.lingala.zip4j.util.Zip4jUtil.isStringNotNullAndNotEmpty;
public class HeaderUtil {
public static FileHeader getFileHeader(ZipModel zipModel, String fileName) throws ZipException {
FileHeader 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 String decodeStringWithCharset(byte[] data, boolean isUtf8Encoded, Charset charset) {
if (InternalZipConstants.CHARSET_UTF_8.equals(charset) && !isUtf8Encoded) {
try {
return new String(data, ZIP_STANDARD_CHARSET);
} catch (UnsupportedEncodingException e) {
return new String(data);
}
}
if(charset != null) {
return new String(data, charset);
}
return new String(data, InternalZipConstants.CHARSET_UTF_8);
}
public static long getOffsetStartOfCentralDirectory(ZipModel zipModel) {
if (zipModel.isZip64Format()) {
return zipModel.getZip64EndOfCentralDirectoryRecord().getOffsetStartCentralDirectoryWRTStartDiskNumber();
}
return zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory();
}
public static List<FileHeader> getFileHeadersUnderDirectory(List<FileHeader> allFileHeaders, FileHeader rootFileHeader) {
if (!rootFileHeader.isDirectory()) {
return Collections.emptyList();
}
return allFileHeaders.stream().filter(e -> e.getFileName().startsWith(rootFileHeader.getFileName())).collect(Collectors.toList());
}
public static long getTotalUncompressedSizeOfAllFileHeaders(List<FileHeader> fileHeaders) {
long totalUncompressedSize = 0;
for (FileHeader fileHeader : fileHeaders) {
if (fileHeader.getZip64ExtendedInfo() != null &&
fileHeader.getZip64ExtendedInfo().getUncompressedSize() > 0) {
totalUncompressedSize += fileHeader.getZip64ExtendedInfo().getUncompressedSize();
} else {
totalUncompressedSize += fileHeader.getUncompressedSize();
}
}
return totalUncompressedSize;
}
private 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;
}
for (FileHeader fileHeader : zipModel.getCentralDirectory().getFileHeaders()) {
String fileNameForHdr = fileHeader.getFileName();
if (!isStringNotNullAndNotEmpty(fileNameForHdr)) {
continue;
}
if (fileName.equalsIgnoreCase(fileNameForHdr)) {
return fileHeader;
}
}
return null;
}
}

View File

@@ -0,0 +1,696 @@
/*
* 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 net.lingala.zip4j.headers;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.io.outputstream.CountingOutputStream;
import net.lingala.zip4j.io.outputstream.OutputStreamWithSplitZipSupport;
import net.lingala.zip4j.io.outputstream.SplitOutputStream;
import net.lingala.zip4j.model.AESExtraDataRecord;
import net.lingala.zip4j.model.ExtraDataRecord;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.Zip64EndOfCentralDirectoryLocator;
import net.lingala.zip4j.model.Zip64EndOfCentralDirectoryRecord;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.RawIO;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
import static net.lingala.zip4j.util.FileUtils.getZipFileNameWithoutExtension;
import static net.lingala.zip4j.util.InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT;
import static net.lingala.zip4j.util.InternalZipConstants.ZIP_64_SIZE_LIMIT;
import static net.lingala.zip4j.util.Zip4jUtil.isStringNotNullAndNotEmpty;
public class HeaderWriter {
private static final short ZIP64_EXTRA_DATA_RECORD_SIZE_LFH = 16;
private static final short ZIP64_EXTRA_DATA_RECORD_SIZE_FH = 28;
private static final short AES_EXTRA_DATA_RECORD_SIZE = 11;
private RawIO rawIO = new RawIO();
private byte[] longBuff = new byte[8];
private byte[] intBuff = new byte[4];
public void writeLocalFileHeader(ZipModel zipModel, LocalFileHeader localFileHeader, OutputStream outputStream,
Charset charset) throws IOException {
try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
rawIO.writeIntLittleEndian(byteArrayOutputStream, (int) localFileHeader.getSignature().getValue());
rawIO.writeShortLittleEndian(byteArrayOutputStream, localFileHeader.getVersionNeededToExtract());
byteArrayOutputStream.write(localFileHeader.getGeneralPurposeFlag());
rawIO.writeShortLittleEndian(byteArrayOutputStream, localFileHeader.getCompressionMethod().getCode());
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getLastModifiedTime());
byteArrayOutputStream.write(longBuff, 0, 4);
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getCrc());
byteArrayOutputStream.write(longBuff, 0, 4);
boolean writeZip64Header = localFileHeader.getCompressedSize() >= ZIP_64_SIZE_LIMIT
|| localFileHeader.getUncompressedSize() >= ZIP_64_SIZE_LIMIT;
if (writeZip64Header) {
rawIO.writeLongLittleEndian(longBuff, 0, ZIP_64_SIZE_LIMIT);
//Set the uncompressed size to ZipConstants.ZIP_64_SIZE_LIMIT as
//these values will be stored in Zip64 extra record
byteArrayOutputStream.write(longBuff, 0, 4);
byteArrayOutputStream.write(longBuff, 0, 4);
zipModel.setZip64Format(true);
localFileHeader.setWriteCompressedSizeInZip64ExtraRecord(true);
} else {
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getCompressedSize());
byteArrayOutputStream.write(longBuff, 0, 4);
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getUncompressedSize());
byteArrayOutputStream.write(longBuff, 0, 4);
localFileHeader.setWriteCompressedSizeInZip64ExtraRecord(false);
}
byte[] fileNameBytes = new byte[0];
if (isStringNotNullAndNotEmpty(localFileHeader.getFileName())) {
fileNameBytes = localFileHeader.getFileName().getBytes(charset);
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileNameBytes.length);
int extraFieldLength = 0;
if (writeZip64Header) {
extraFieldLength += ZIP64_EXTRA_DATA_RECORD_SIZE_LFH + 4; // 4 for signature + size of record
}
if (localFileHeader.getAesExtraDataRecord() != null) {
extraFieldLength += AES_EXTRA_DATA_RECORD_SIZE;
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, extraFieldLength);
if (fileNameBytes.length > 0) {
byteArrayOutputStream.write(fileNameBytes);
}
//Zip64 should be the first extra data record that should be written
//This is NOT according to any specification but if this is changed
//corresponding logic for updateLocalFileHeader for compressed size
//has to be modified as well
if (writeZip64Header) {
rawIO.writeShortLittleEndian(byteArrayOutputStream,
(int) HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue());
rawIO.writeShortLittleEndian(byteArrayOutputStream, ZIP64_EXTRA_DATA_RECORD_SIZE_LFH);
rawIO.writeLongLittleEndian(byteArrayOutputStream, localFileHeader.getUncompressedSize());
rawIO.writeLongLittleEndian(byteArrayOutputStream, localFileHeader.getCompressedSize());
}
if (localFileHeader.getAesExtraDataRecord() != null) {
AESExtraDataRecord aesExtraDataRecord = localFileHeader.getAesExtraDataRecord();
rawIO.writeShortLittleEndian(byteArrayOutputStream, (int) aesExtraDataRecord.getSignature().getValue());
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getDataSize());
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getAesVersion().getVersionNumber());
byteArrayOutputStream.write(aesExtraDataRecord.getVendorID().getBytes());
byte[] aesStrengthBytes = new byte[1];
aesStrengthBytes[0] = (byte) aesExtraDataRecord.getAesKeyStrength().getRawCode();
byteArrayOutputStream.write(aesStrengthBytes);
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getCompressionMethod().getCode());
}
outputStream.write(byteArrayOutputStream.toByteArray());
}
}
public void writeExtendedLocalHeader(LocalFileHeader localFileHeader, OutputStream outputStream)
throws IOException {
if (localFileHeader == null || outputStream == null) {
throw new ZipException("input parameters is null, cannot write extended local header");
}
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
rawIO.writeIntLittleEndian(byteArrayOutputStream, (int) HeaderSignature.EXTRA_DATA_RECORD.getValue());
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getCrc());
byteArrayOutputStream.write(longBuff, 0, 4);
if (localFileHeader.isWriteCompressedSizeInZip64ExtraRecord()) {
rawIO.writeLongLittleEndian(byteArrayOutputStream, localFileHeader.getCompressedSize());
rawIO.writeLongLittleEndian(byteArrayOutputStream, localFileHeader.getUncompressedSize());
} else {
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getCompressedSize());
byteArrayOutputStream.write(longBuff, 0, 4);
rawIO.writeLongLittleEndian(longBuff, 0, localFileHeader.getUncompressedSize());
byteArrayOutputStream.write(longBuff, 0, 4);
}
outputStream.write(byteArrayOutputStream.toByteArray());
}
}
public void finalizeZipFile(ZipModel zipModel, OutputStream outputStream, Charset charset) throws IOException {
if (zipModel == null || outputStream == null) {
throw new ZipException("input parameters is null, cannot finalize zip file");
}
try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
processHeaderData(zipModel, outputStream);
long offsetCentralDir = getOffsetOfCentralDirectory(zipModel);
writeCentralDirectory(zipModel, byteArrayOutputStream, rawIO, charset);
int sizeOfCentralDir = byteArrayOutputStream.size();
if (zipModel.isZip64Format() || offsetCentralDir >= InternalZipConstants.ZIP_64_SIZE_LIMIT
|| zipModel.getCentralDirectory().getFileHeaders().size() >= InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
if (zipModel.getZip64EndOfCentralDirectoryRecord() == null) {
zipModel.setZip64EndOfCentralDirectoryRecord(new Zip64EndOfCentralDirectoryRecord());
}
if (zipModel.getZip64EndOfCentralDirectoryLocator() == null) {
zipModel.setZip64EndOfCentralDirectoryLocator(new Zip64EndOfCentralDirectoryLocator());
}
zipModel.getZip64EndOfCentralDirectoryLocator().setOffsetZip64EndOfCentralDirectoryRecord(offsetCentralDir
+ sizeOfCentralDir);
if (isSplitZipFile(outputStream)) {
int currentSplitFileCounter = getCurrentSplitFileCounter(outputStream);
zipModel.getZip64EndOfCentralDirectoryLocator().setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(
currentSplitFileCounter);
zipModel.getZip64EndOfCentralDirectoryLocator().setTotalNumberOfDiscs(currentSplitFileCounter + 1);
} else {
zipModel.getZip64EndOfCentralDirectoryLocator().setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(0);
zipModel.getZip64EndOfCentralDirectoryLocator().setTotalNumberOfDiscs(1);
}
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectoryRecord = buildZip64EndOfCentralDirectoryRecord(zipModel,
sizeOfCentralDir, offsetCentralDir);
zipModel.setZip64EndOfCentralDirectoryRecord(zip64EndOfCentralDirectoryRecord);
writeZip64EndOfCentralDirectoryRecord(zip64EndOfCentralDirectoryRecord, byteArrayOutputStream,rawIO);
writeZip64EndOfCentralDirectoryLocator(zipModel.getZip64EndOfCentralDirectoryLocator(), byteArrayOutputStream, rawIO);
}
writeEndOfCentralDirectoryRecord(zipModel, sizeOfCentralDir, offsetCentralDir, byteArrayOutputStream, rawIO, charset);
writeZipHeaderBytes(zipModel, outputStream, byteArrayOutputStream.toByteArray(), charset);
}
}
public void finalizeZipFileWithoutValidations(ZipModel zipModel, OutputStream outputStream, Charset charset) throws IOException {
if (zipModel == null || outputStream == null) {
throw new ZipException("input parameters is null, cannot finalize zip file without validations");
}
try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
long offsetCentralDir = zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory();
writeCentralDirectory(zipModel, byteArrayOutputStream, rawIO, charset);
int sizeOfCentralDir = byteArrayOutputStream.size();
if (zipModel.isZip64Format() || offsetCentralDir >= InternalZipConstants.ZIP_64_SIZE_LIMIT
|| zipModel.getCentralDirectory().getFileHeaders().size() >= InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
if (zipModel.getZip64EndOfCentralDirectoryRecord() == null) {
zipModel.setZip64EndOfCentralDirectoryRecord(new Zip64EndOfCentralDirectoryRecord());
}
if (zipModel.getZip64EndOfCentralDirectoryLocator() == null) {
zipModel.setZip64EndOfCentralDirectoryLocator(new Zip64EndOfCentralDirectoryLocator());
}
zipModel.getZip64EndOfCentralDirectoryLocator().setOffsetZip64EndOfCentralDirectoryRecord(offsetCentralDir
+ sizeOfCentralDir);
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectoryRecord = buildZip64EndOfCentralDirectoryRecord(zipModel,
sizeOfCentralDir, offsetCentralDir);
zipModel.setZip64EndOfCentralDirectoryRecord(zip64EndOfCentralDirectoryRecord);
writeZip64EndOfCentralDirectoryRecord(zip64EndOfCentralDirectoryRecord, byteArrayOutputStream,rawIO);
writeZip64EndOfCentralDirectoryLocator(zipModel.getZip64EndOfCentralDirectoryLocator(), byteArrayOutputStream, rawIO);
}
writeEndOfCentralDirectoryRecord(zipModel, sizeOfCentralDir, offsetCentralDir, byteArrayOutputStream, rawIO, charset);
writeZipHeaderBytes(zipModel, outputStream, byteArrayOutputStream.toByteArray(), charset);
}
}
public void updateLocalFileHeader(FileHeader fileHeader, ZipModel zipModel, SplitOutputStream outputStream)
throws IOException {
if (fileHeader == null || zipModel == null) {
throw new ZipException("invalid input parameters, cannot update local file header");
}
boolean closeFlag = false;
SplitOutputStream currOutputStream;
if (fileHeader.getDiskNumberStart() != outputStream.getCurrentSplitFileCounter()) {
String parentFile = zipModel.getZipFile().getParent();
String fileNameWithoutExt = getZipFileNameWithoutExtension(zipModel.getZipFile().getName());
String fileName = parentFile + System.getProperty("file.separator");
if (fileHeader.getDiskNumberStart() < 9) {
fileName += fileNameWithoutExt + ".z0" + (fileHeader.getDiskNumberStart() + 1);
} else {
fileName += fileNameWithoutExt + ".z" + (fileHeader.getDiskNumberStart() + 1);
}
currOutputStream = new SplitOutputStream(new File(fileName));
closeFlag = true;
} else {
currOutputStream = outputStream;
}
long currOffset = currOutputStream.getFilePointer();
currOutputStream.seek(fileHeader.getOffsetLocalHeader() + InternalZipConstants.UPDATE_LFH_CRC);
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getCrc());
currOutputStream.write(longBuff, 0, 4);
updateFileSizesInLocalFileHeader(currOutputStream, fileHeader);
if (closeFlag) {
currOutputStream.close();
} else {
outputStream.seek(currOffset);
}
}
private void updateFileSizesInLocalFileHeader(SplitOutputStream outputStream, FileHeader fileHeader)
throws IOException {
if (fileHeader.getUncompressedSize() >= ZIP_64_SIZE_LIMIT) {
rawIO.writeLongLittleEndian(longBuff, 0, ZIP_64_SIZE_LIMIT);
outputStream.write(longBuff, 0, 4);
outputStream.write(longBuff, 0, 4);
//2 - file name length
//2 - extra field length
//variable - file name which can be determined by fileNameLength
//2 - Zip64 signature
//2 - size of zip64 data
//8 - uncompressed size
//8 - compressed size
int zip64CompressedSizeOffset = 2 + 2 + fileHeader.getFileNameLength() + 2 + 2;
if (outputStream.skipBytes(zip64CompressedSizeOffset) != zip64CompressedSizeOffset) {
throw new ZipException("Unable to skip " + zip64CompressedSizeOffset + " bytes to update LFH");
}
rawIO.writeLongLittleEndian(outputStream, fileHeader.getUncompressedSize());
rawIO.writeLongLittleEndian(outputStream, fileHeader.getCompressedSize());
} else {
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getCompressedSize());
outputStream.write(longBuff, 0, 4);
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getUncompressedSize());
outputStream.write(longBuff, 0, 4);
}
}
private boolean isSplitZipFile(OutputStream outputStream) {
if (outputStream instanceof SplitOutputStream) {
return ((SplitOutputStream) outputStream).isSplitZipFile();
} else if (outputStream instanceof CountingOutputStream) {
return ((CountingOutputStream) outputStream).isSplitZipFile();
}
return false;
}
private int getCurrentSplitFileCounter(OutputStream outputStream) {
if (outputStream instanceof SplitOutputStream) {
return ((SplitOutputStream) outputStream).getCurrentSplitFileCounter();
}
return ((CountingOutputStream) outputStream).getCurrentSplitFileCounter();
}
private void writeZipHeaderBytes(ZipModel zipModel, OutputStream outputStream, byte[] buff, Charset charset) throws IOException {
if (buff == null) {
throw new ZipException("invalid buff to write as zip headers");
}
if (outputStream instanceof CountingOutputStream) {
if (((CountingOutputStream) outputStream).checkBuffSizeAndStartNextSplitFile(buff.length)) {
finalizeZipFile(zipModel, outputStream, charset);
return;
}
}
outputStream.write(buff);
}
private void processHeaderData(ZipModel zipModel, OutputStream outputStream) throws IOException {
int currentSplitFileCounter = 0;
if (outputStream instanceof OutputStreamWithSplitZipSupport) {
zipModel.getEndOfCentralDirectoryRecord().setOffsetOfStartOfCentralDirectory(
((OutputStreamWithSplitZipSupport) outputStream).getFilePointer());
currentSplitFileCounter = ((OutputStreamWithSplitZipSupport) outputStream).getCurrentSplitFileCounter();
}
if (zipModel.isZip64Format()) {
if (zipModel.getZip64EndOfCentralDirectoryRecord() == null) {
zipModel.setZip64EndOfCentralDirectoryRecord(new Zip64EndOfCentralDirectoryRecord());
}
if (zipModel.getZip64EndOfCentralDirectoryLocator() == null) {
zipModel.setZip64EndOfCentralDirectoryLocator(new Zip64EndOfCentralDirectoryLocator());
}
zipModel.getZip64EndOfCentralDirectoryRecord().setOffsetStartCentralDirectoryWRTStartDiskNumber(
zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory());
zipModel.getZip64EndOfCentralDirectoryLocator().setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(
currentSplitFileCounter);
zipModel.getZip64EndOfCentralDirectoryLocator().setTotalNumberOfDiscs(currentSplitFileCounter + 1);
}
zipModel.getEndOfCentralDirectoryRecord().setNumberOfThisDisk(currentSplitFileCounter);
zipModel.getEndOfCentralDirectoryRecord().setNumberOfThisDiskStartOfCentralDir(currentSplitFileCounter);
}
private void writeCentralDirectory(ZipModel zipModel, ByteArrayOutputStream byteArrayOutputStream, RawIO rawIO, Charset charset)
throws ZipException {
if (zipModel.getCentralDirectory() == null || zipModel.getCentralDirectory().getFileHeaders() == null
|| zipModel.getCentralDirectory().getFileHeaders().size() <= 0) {
return;
}
for (FileHeader fileHeader: zipModel.getCentralDirectory().getFileHeaders()) {
writeFileHeader(zipModel, fileHeader, byteArrayOutputStream, rawIO, charset);
}
}
private void writeFileHeader(ZipModel zipModel, FileHeader fileHeader, ByteArrayOutputStream byteArrayOutputStream,
RawIO rawIO, Charset charset) throws ZipException {
if (fileHeader == null) {
throw new ZipException("input parameters is null, cannot write local file header");
}
try {
final byte[] emptyShortByte = {0, 0};
boolean writeZip64ExtendedInfo = isZip64Entry(fileHeader);
rawIO.writeIntLittleEndian(byteArrayOutputStream, (int) fileHeader.getSignature().getValue());
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileHeader.getVersionMadeBy());
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileHeader.getVersionNeededToExtract());
byteArrayOutputStream.write(fileHeader.getGeneralPurposeFlag());
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileHeader.getCompressionMethod().getCode());
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getLastModifiedTime());
byteArrayOutputStream.write(longBuff, 0, 4);
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getCrc());
byteArrayOutputStream.write(longBuff, 0, 4);
if (writeZip64ExtendedInfo) {
rawIO.writeLongLittleEndian(longBuff, 0, ZIP_64_SIZE_LIMIT);
byteArrayOutputStream.write(longBuff, 0, 4);
byteArrayOutputStream.write(longBuff, 0, 4);
zipModel.setZip64Format(true);
} else {
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getCompressedSize());
byteArrayOutputStream.write(longBuff, 0, 4);
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getUncompressedSize());
byteArrayOutputStream.write(longBuff, 0, 4);
}
byte[] fileNameBytes = new byte[0];
if (isStringNotNullAndNotEmpty(fileHeader.getFileName())) {
fileNameBytes = fileHeader.getFileName().getBytes(charset);
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileNameBytes.length);
//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 (writeZip64ExtendedInfo) {
rawIO.writeLongLittleEndian(longBuff, 0, ZIP_64_SIZE_LIMIT);
System.arraycopy(longBuff, 0, offsetLocalHeaderBytes, 0, 4);
} else {
rawIO.writeLongLittleEndian(longBuff, 0, fileHeader.getOffsetLocalHeader());
System.arraycopy(longBuff, 0, offsetLocalHeaderBytes, 0, 4);
}
int extraFieldLength = calculateExtraDataRecordsSize(fileHeader, writeZip64ExtendedInfo);
rawIO.writeShortLittleEndian(byteArrayOutputStream, extraFieldLength);
String fileComment = fileHeader.getFileComment();
byte[] fileCommentBytes = new byte[0];
if (isStringNotNullAndNotEmpty(fileComment)) {
fileCommentBytes = fileComment.getBytes(charset);
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileCommentBytes.length);
if (writeZip64ExtendedInfo) {
rawIO.writeIntLittleEndian(intBuff, 0, ZIP_64_NUMBER_OF_ENTRIES_LIMIT);
byteArrayOutputStream.write(intBuff, 0, 2);
} else {
rawIO.writeShortLittleEndian(byteArrayOutputStream, fileHeader.getDiskNumberStart());
}
byteArrayOutputStream.write(emptyShortByte);
//External file attributes
byteArrayOutputStream.write(fileHeader.getExternalFileAttributes());
//offset local header - this data is computed above
byteArrayOutputStream.write(offsetLocalHeaderBytes);
if (fileNameBytes.length > 0) {
byteArrayOutputStream.write(fileNameBytes);
}
if (writeZip64ExtendedInfo) {
zipModel.setZip64Format(true);
//Zip64 header
rawIO.writeShortLittleEndian(byteArrayOutputStream,
(int) HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue());
//size of data
rawIO.writeShortLittleEndian(byteArrayOutputStream, ZIP64_EXTRA_DATA_RECORD_SIZE_FH);
rawIO.writeLongLittleEndian(byteArrayOutputStream, fileHeader.getUncompressedSize());
rawIO.writeLongLittleEndian(byteArrayOutputStream, fileHeader.getCompressedSize());
rawIO.writeLongLittleEndian(byteArrayOutputStream, fileHeader.getOffsetLocalHeader());
rawIO.writeIntLittleEndian(byteArrayOutputStream, fileHeader.getDiskNumberStart());
}
if (fileHeader.getAesExtraDataRecord() != null) {
AESExtraDataRecord aesExtraDataRecord = fileHeader.getAesExtraDataRecord();
rawIO.writeShortLittleEndian(byteArrayOutputStream, (int) aesExtraDataRecord.getSignature().getValue());
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getDataSize());
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getAesVersion().getVersionNumber());
byteArrayOutputStream.write(aesExtraDataRecord.getVendorID().getBytes());
byte[] aesStrengthBytes = new byte[1];
aesStrengthBytes[0] = (byte) aesExtraDataRecord.getAesKeyStrength().getRawCode();
byteArrayOutputStream.write(aesStrengthBytes);
rawIO.writeShortLittleEndian(byteArrayOutputStream, aesExtraDataRecord.getCompressionMethod().getCode());
}
writeRemainingExtraDataRecordsIfPresent(fileHeader, byteArrayOutputStream);
if (fileCommentBytes.length > 0) {
byteArrayOutputStream.write(fileCommentBytes);
}
} catch (Exception e) {
throw new ZipException(e);
}
}
private int calculateExtraDataRecordsSize(FileHeader fileHeader, boolean writeZip64ExtendedInfo) throws IOException {
int extraFieldLength = 0;
if (writeZip64ExtendedInfo) {
extraFieldLength += ZIP64_EXTRA_DATA_RECORD_SIZE_FH + 4; // 4 for signature + size of record
}
if (fileHeader.getAesExtraDataRecord() != null) {
extraFieldLength += AES_EXTRA_DATA_RECORD_SIZE;
}
if (fileHeader.getExtraDataRecords() != null) {
for (ExtraDataRecord extraDataRecord : fileHeader.getExtraDataRecords()) {
if (extraDataRecord.getHeader() == HeaderSignature.AES_EXTRA_DATA_RECORD.getValue()
|| extraDataRecord.getHeader() == HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue()) {
continue;
}
extraFieldLength += 4 + extraDataRecord.getSizeOfData(); // 4 = 2 for header + 2 for size of data
}
}
return extraFieldLength;
}
private void writeRemainingExtraDataRecordsIfPresent(FileHeader fileHeader, OutputStream outputStream) throws IOException {
if (fileHeader.getExtraDataRecords() == null || fileHeader.getExtraDataRecords().size() == 0) {
return;
}
for (ExtraDataRecord extraDataRecord : fileHeader.getExtraDataRecords()) {
if (extraDataRecord.getHeader() == HeaderSignature.AES_EXTRA_DATA_RECORD.getValue()
|| extraDataRecord.getHeader() == HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue()) {
continue;
}
rawIO.writeShortLittleEndian(outputStream, (int) extraDataRecord.getHeader());
rawIO.writeShortLittleEndian(outputStream, extraDataRecord.getSizeOfData());
if (extraDataRecord.getSizeOfData() > 0 && extraDataRecord.getData() != null) {
outputStream.write(extraDataRecord.getData());
}
}
}
private void writeZip64EndOfCentralDirectoryRecord(Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectoryRecord,
ByteArrayOutputStream byteArrayOutputStream, RawIO rawIO) throws IOException {
rawIO.writeIntLittleEndian(byteArrayOutputStream, (int) zip64EndOfCentralDirectoryRecord.getSignature().getValue());
rawIO.writeLongLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getSizeOfZip64EndCentralDirectoryRecord());
rawIO.writeShortLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getVersionMadeBy());
rawIO.writeShortLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getVersionNeededToExtract());
rawIO.writeIntLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getNumberOfThisDisk());
rawIO.writeIntLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getNumberOfThisDiskStartOfCentralDirectory());
rawIO.writeLongLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getTotalNumberOfEntriesInCentralDirectoryOnThisDisk());
rawIO.writeLongLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getTotalNumberOfEntriesInCentralDirectory());
rawIO.writeLongLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getSizeOfCentralDirectory());
rawIO.writeLongLittleEndian(byteArrayOutputStream, zip64EndOfCentralDirectoryRecord.getOffsetStartCentralDirectoryWRTStartDiskNumber());
}
private void writeZip64EndOfCentralDirectoryLocator(Zip64EndOfCentralDirectoryLocator zip64EndOfCentralDirectoryLocator,
ByteArrayOutputStream byteArrayOutputStream,
RawIO rawIO) throws IOException {
rawIO.writeIntLittleEndian(byteArrayOutputStream, (int) HeaderSignature.ZIP64_END_CENTRAL_DIRECTORY_LOCATOR.getValue());
rawIO.writeIntLittleEndian(byteArrayOutputStream,
zip64EndOfCentralDirectoryLocator.getNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord());
rawIO.writeLongLittleEndian(byteArrayOutputStream,
zip64EndOfCentralDirectoryLocator.getOffsetZip64EndOfCentralDirectoryRecord());
rawIO.writeIntLittleEndian(byteArrayOutputStream,
zip64EndOfCentralDirectoryLocator.getTotalNumberOfDiscs());
}
private void writeEndOfCentralDirectoryRecord(ZipModel zipModel, int sizeOfCentralDir, long offsetCentralDir,
ByteArrayOutputStream byteArrayOutputStream, RawIO rawIO, Charset charset)
throws IOException {
byte[] longByte = new byte[8];
rawIO.writeIntLittleEndian(byteArrayOutputStream, (int) HeaderSignature.END_OF_CENTRAL_DIRECTORY.getValue());
rawIO.writeShortLittleEndian(byteArrayOutputStream,
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk());
rawIO.writeShortLittleEndian(byteArrayOutputStream,
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDiskStartOfCentralDir());
long numEntries = zipModel.getCentralDirectory().getFileHeaders().size();
long numEntriesOnThisDisk = numEntries;
if (zipModel.isSplitArchive()) {
numEntriesOnThisDisk = countNumberOfFileHeaderEntriesOnDisk(zipModel.getCentralDirectory().getFileHeaders(),
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk());
}
if (numEntriesOnThisDisk > InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
numEntriesOnThisDisk = InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT;
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, (int) numEntriesOnThisDisk);
if (numEntries > InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT) {
numEntries = InternalZipConstants.ZIP_64_NUMBER_OF_ENTRIES_LIMIT;
}
rawIO.writeShortLittleEndian(byteArrayOutputStream, (int) numEntries);
rawIO.writeIntLittleEndian(byteArrayOutputStream, sizeOfCentralDir);
if (offsetCentralDir > ZIP_64_SIZE_LIMIT) {
rawIO.writeLongLittleEndian(longByte, 0, ZIP_64_SIZE_LIMIT);
byteArrayOutputStream.write(longByte, 0, 4);
} else {
rawIO.writeLongLittleEndian(longByte, 0, offsetCentralDir);
byteArrayOutputStream.write(longByte, 0, 4);
}
String comment = zipModel.getEndOfCentralDirectoryRecord().getComment();
if (isStringNotNullAndNotEmpty(comment)) {
byte[] commentBytes = comment.getBytes(charset);
rawIO.writeShortLittleEndian(byteArrayOutputStream, commentBytes.length);
byteArrayOutputStream.write(commentBytes);
} else {
rawIO.writeShortLittleEndian(byteArrayOutputStream, 0);
}
}
private long countNumberOfFileHeaderEntriesOnDisk(List<FileHeader> 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 (FileHeader fileHeader : fileHeaders) {
if (fileHeader.getDiskNumberStart() == numOfDisk) {
noEntries++;
}
}
return noEntries;
}
private boolean isZip64Entry(FileHeader fileHeader) {
return fileHeader.getCompressedSize() >= ZIP_64_SIZE_LIMIT
|| fileHeader.getUncompressedSize() >= ZIP_64_SIZE_LIMIT
|| fileHeader.getOffsetLocalHeader() >= ZIP_64_SIZE_LIMIT
|| fileHeader.getDiskNumberStart() >= ZIP_64_NUMBER_OF_ENTRIES_LIMIT;
}
private long getOffsetOfCentralDirectory(ZipModel zipModel) {
if (zipModel.isZip64Format()
&& zipModel.getZip64EndOfCentralDirectoryRecord() != null
&& zipModel.getZip64EndOfCentralDirectoryRecord().getOffsetStartCentralDirectoryWRTStartDiskNumber() != -1) {
return zipModel.getZip64EndOfCentralDirectoryRecord().getOffsetStartCentralDirectoryWRTStartDiskNumber();
}
return zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory();
}
private Zip64EndOfCentralDirectoryRecord buildZip64EndOfCentralDirectoryRecord(ZipModel zipModel, int sizeOfCentralDir,
long offsetCentralDir) throws ZipException {
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectoryRecord = new Zip64EndOfCentralDirectoryRecord();
zip64EndOfCentralDirectoryRecord.setSignature(HeaderSignature.ZIP64_END_CENTRAL_DIRECTORY_RECORD);
zip64EndOfCentralDirectoryRecord.setSizeOfZip64EndCentralDirectoryRecord(44);
if (zipModel.getCentralDirectory() != null &&
zipModel.getCentralDirectory().getFileHeaders() != null &&
zipModel.getCentralDirectory().getFileHeaders().size() > 0) {
FileHeader firstFileHeader = zipModel.getCentralDirectory().getFileHeaders().get(0);
zip64EndOfCentralDirectoryRecord.setVersionMadeBy(firstFileHeader.getVersionMadeBy());
zip64EndOfCentralDirectoryRecord.setVersionNeededToExtract(firstFileHeader.getVersionNeededToExtract());
}
zip64EndOfCentralDirectoryRecord.setNumberOfThisDisk(zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk());
zip64EndOfCentralDirectoryRecord.setNumberOfThisDiskStartOfCentralDirectory(zipModel.getEndOfCentralDirectoryRecord()
.getNumberOfThisDiskStartOfCentralDir());
long numEntries = zipModel.getCentralDirectory().getFileHeaders().size();
long numEntriesOnThisDisk = numEntries;
if (zipModel.isSplitArchive()) {
numEntriesOnThisDisk = countNumberOfFileHeaderEntriesOnDisk(zipModel.getCentralDirectory().getFileHeaders(),
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk());
}
zip64EndOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(numEntriesOnThisDisk);
zip64EndOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectory(numEntries);
zip64EndOfCentralDirectoryRecord.setSizeOfCentralDirectory(sizeOfCentralDir);
zip64EndOfCentralDirectoryRecord.setOffsetStartCentralDirectoryWRTStartDiskNumber(offsetCentralDir);
return zip64EndOfCentralDirectoryRecord;
}
}

View File

@@ -0,0 +1,18 @@
package net.lingala.zip4j.headers;
public enum VersionMadeBy {
SPECIFICATION_VERSION((byte) 51),
WINDOWS((byte) 0),
UNIX((byte) 3);
private byte code;
VersionMadeBy(byte code) {
this.code = code;
}
public byte getCode() {
return code;
}
}

View File

@@ -0,0 +1,19 @@
package net.lingala.zip4j.headers;
public enum VersionNeededToExtract {
DEFAULT(10),
DEFLATE_COMPRESSED(20),
ZIP_64_FORMAT(45),
AES_ENCRYPTED(51);
private int code;
VersionNeededToExtract(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,187 @@
package net.lingala.zip4j.io.inputstream;
import net.lingala.zip4j.crypto.AESDecrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.AESExtraDataRecord;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.Zip4jUtil;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import static net.lingala.zip4j.util.InternalZipConstants.AES_AUTH_LENGTH;
import static net.lingala.zip4j.util.Zip4jUtil.readFully;
class AesCipherInputStream extends CipherInputStream<AESDecrypter> {
private byte[] singleByteBuffer = new byte[1];
private byte[] aes16ByteBlock = new byte[16];
private int aes16ByteBlockPointer = 0;
private int remainingAes16ByteBlockLength = 0;
private int lengthToRead = 0;
private int offsetWithAesBlock = 0;
private int bytesCopiedInThisIteration = 0;
private int lengthToCopyInThisIteration = 0;
private int aes16ByteBlockReadLength = 0;
public AesCipherInputStream(ZipEntryInputStream zipEntryInputStream, LocalFileHeader localFileHeader, char[] password)
throws IOException {
super(zipEntryInputStream, localFileHeader, password);
}
@Override
protected AESDecrypter initializeDecrypter(LocalFileHeader localFileHeader, char[] password) throws IOException {
return new AESDecrypter(localFileHeader.getAesExtraDataRecord(), password, getSalt(localFileHeader), getPasswordVerifier());
}
@Override
public int read() throws IOException {
int readLen = read(singleByteBuffer);
if (readLen == -1) {
return -1;
}
return singleByteBuffer[0];
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
lengthToRead = len;
offsetWithAesBlock = off;
bytesCopiedInThisIteration = 0;
if (remainingAes16ByteBlockLength != 0) {
copyBytesFromBuffer(b, offsetWithAesBlock);
if (bytesCopiedInThisIteration == len) {
return bytesCopiedInThisIteration;
}
}
if (lengthToRead < 16) {
aes16ByteBlockReadLength = super.read(aes16ByteBlock, 0, aes16ByteBlock.length);
aes16ByteBlockPointer = 0;
if (aes16ByteBlockReadLength == -1) {
remainingAes16ByteBlockLength = 0;
if (bytesCopiedInThisIteration > 0) {
return bytesCopiedInThisIteration;
}
return -1;
}
remainingAes16ByteBlockLength = aes16ByteBlockReadLength;
copyBytesFromBuffer(b, offsetWithAesBlock);
if (bytesCopiedInThisIteration == len) {
return bytesCopiedInThisIteration;
}
}
int readLen = super.read(b, offsetWithAesBlock, (lengthToRead - lengthToRead %16));
if (readLen == -1) {
if (bytesCopiedInThisIteration > 0) {
return bytesCopiedInThisIteration;
} else {
return -1;
}
} else {
return readLen + bytesCopiedInThisIteration;
}
}
private void copyBytesFromBuffer(byte[] b, int off) {
lengthToCopyInThisIteration = lengthToRead < remainingAes16ByteBlockLength ? lengthToRead : remainingAes16ByteBlockLength;
System.arraycopy(aes16ByteBlock, aes16ByteBlockPointer, b, off, lengthToCopyInThisIteration);
incrementAesByteBlockPointer(lengthToCopyInThisIteration);
decrementRemainingAesBytesLength(lengthToCopyInThisIteration);
bytesCopiedInThisIteration += lengthToCopyInThisIteration;
lengthToRead -= lengthToCopyInThisIteration;
offsetWithAesBlock += lengthToCopyInThisIteration;
}
@Override
protected void endOfEntryReached(InputStream inputStream) throws IOException {
verifyContent(readStoredMac(inputStream));
}
private void verifyContent(byte[] storedMac) throws IOException {
if (getLocalFileHeader().isDataDescriptorExists()
&& CompressionMethod.DEFLATE.equals(Zip4jUtil.getCompressionMethod(getLocalFileHeader()))) {
// Skip content verification in case of Deflate compression and if data descriptor exists.
// In this case, we do not know the exact size of compressed data before hand and it is possible that we read
// and pass more than required data into inflater, thereby corrupting the aes mac bytes.
// See usage of PushBackInputStream in the project for how this push back of data is done
// Unfortunately, in this case we cannot perform a content verification and have to skip
return;
}
byte[] calculatedMac = getDecrypter().getCalculatedAuthenticationBytes();
byte[] first10BytesOfCalculatedMac = new byte[AES_AUTH_LENGTH];
System.arraycopy(calculatedMac, 0, first10BytesOfCalculatedMac, 0, InternalZipConstants.AES_AUTH_LENGTH);
if (!Arrays.equals(storedMac, first10BytesOfCalculatedMac)) {
throw new IOException("Reached end of data for this entry, but aes verification failed");
}
}
protected byte[] readStoredMac(InputStream inputStream) throws IOException {
byte[] storedMac = new byte[AES_AUTH_LENGTH];
int readLen = readFully(inputStream, storedMac);
if (readLen != AES_AUTH_LENGTH) {
throw new ZipException("Invalid AES Mac bytes. Could not read sufficient data");
}
return storedMac;
}
private byte[] getSalt(LocalFileHeader localFileHeader) throws IOException {
if (localFileHeader.getAesExtraDataRecord() == null) {
throw new IOException("invalid aes extra data record");
}
AESExtraDataRecord aesExtraDataRecord = localFileHeader.getAesExtraDataRecord();
byte[] saltBytes = new byte[aesExtraDataRecord.getAesKeyStrength().getSaltLength()];
readRaw(saltBytes);
return saltBytes;
}
private byte[] getPasswordVerifier() throws IOException {
byte[] pvBytes = new byte[2];
readRaw(pvBytes);
return pvBytes;
}
private void incrementAesByteBlockPointer(int incrementBy) {
aes16ByteBlockPointer += incrementBy;
if (aes16ByteBlockPointer >= 15) {
aes16ByteBlockPointer = 15;
}
}
private void decrementRemainingAesBytesLength(int decrementBy) {
remainingAes16ByteBlockLength -= decrementBy;
if (remainingAes16ByteBlockLength <= 0) {
remainingAes16ByteBlockLength = 0;
}
}
}

View File

@@ -0,0 +1,97 @@
package net.lingala.zip4j.io.inputstream;
import net.lingala.zip4j.crypto.Decrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.Zip4jUtil;
import java.io.IOException;
import java.io.InputStream;
import static net.lingala.zip4j.util.Zip4jUtil.readFully;
abstract class CipherInputStream<T extends Decrypter> extends InputStream {
private ZipEntryInputStream zipEntryInputStream;
private T decrypter;
private byte[] lastReadRawDataCache;
private byte[] singleByteBuffer = new byte[1];
private LocalFileHeader localFileHeader;
public CipherInputStream(ZipEntryInputStream zipEntryInputStream, LocalFileHeader localFileHeader, char[] password) throws IOException, ZipException {
this.zipEntryInputStream = zipEntryInputStream;
this.decrypter = initializeDecrypter(localFileHeader, password);
this.localFileHeader = localFileHeader;
if (Zip4jUtil.getCompressionMethod(localFileHeader).equals(CompressionMethod.DEFLATE)) {
lastReadRawDataCache = new byte[InternalZipConstants.BUFF_SIZE];
}
}
@Override
public int read() throws IOException {
int readLen = read(singleByteBuffer);
if (readLen == -1) {
return -1;
}
return singleByteBuffer[0] & 0xff;
}
@Override
public int read(byte[] b) throws IOException {
return this.read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int readLen = readFully(zipEntryInputStream, b, off, len);
if (readLen > 0) {
cacheRawData(b, readLen);
decrypter.decryptData(b, off, readLen);
}
return readLen;
}
@Override
public void close() throws IOException {
zipEntryInputStream.close();
}
public byte[] getLastReadRawDataCache() {
return lastReadRawDataCache;
}
protected int readRaw(byte[] b) throws IOException {
return zipEntryInputStream.readRawFully(b);
}
private void cacheRawData(byte[] b, int len) {
if (lastReadRawDataCache != null) {
System.arraycopy(b, 0, lastReadRawDataCache, 0, len);
}
}
public T getDecrypter() {
return decrypter;
}
protected void endOfEntryReached(InputStream inputStream) throws IOException {
// is optional but useful for AES
}
protected long getNumberOfBytesReadForThisEntry() {
return zipEntryInputStream.getNumberOfBytesRead();
}
public LocalFileHeader getLocalFileHeader() {
return localFileHeader;
}
protected abstract T initializeDecrypter(LocalFileHeader localFileHeader, char[] password) throws IOException, ZipException;
}

View File

@@ -0,0 +1,53 @@
package net.lingala.zip4j.io.inputstream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
abstract class DecompressedInputStream extends InputStream {
private CipherInputStream cipherInputStream;
protected byte[] oneByteBuffer = new byte[1];
public DecompressedInputStream(CipherInputStream cipherInputStream) {
this.cipherInputStream = cipherInputStream;
}
@Override
public int read() throws IOException {
int readLen = read(oneByteBuffer);
if (readLen == -1) {
return -1;
}
return oneByteBuffer[0];
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return cipherInputStream.read(b, off, len);
}
@Override
public void close() throws IOException {
cipherInputStream.close();
}
public void endOfEntryReached(InputStream inputStream) throws IOException {
cipherInputStream.endOfEntryReached(inputStream);
}
public void pushBackInputStreamIfNecessary(PushbackInputStream pushbackInputStream) throws IOException {
// Do nothing by default
}
protected byte[] getLastReadRawDataCache() {
return cipherInputStream.getLastReadRawDataCache();
}
}

View File

@@ -0,0 +1,92 @@
package net.lingala.zip4j.io.inputstream;
import net.lingala.zip4j.util.InternalZipConstants;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
public class InflaterInputStream extends DecompressedInputStream {
private Inflater inflater;
private byte[] buff;
private byte[] singleByteBuffer = new byte[1];
private int len;
public InflaterInputStream(CipherInputStream cipherInputStream) {
super(cipherInputStream);
this.inflater = new Inflater(true);
buff = new byte[InternalZipConstants.BUFF_SIZE];
}
@Override
public int read() throws IOException {
int readLen = read(singleByteBuffer);
if (readLen == -1) {
return -1;
}
return singleByteBuffer[0];
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
try {
int n;
while ((n = inflater.inflate(b, off, len)) == 0) {
if (inflater.finished() || inflater.needsDictionary()) {
return -1;
}
if (inflater.needsInput()) {
fill();
}
}
return n;
} catch (DataFormatException e) {
throw new IOException(e);
}
}
@Override
public void endOfEntryReached(InputStream inputStream) throws IOException {
if (inflater != null) {
inflater.end();
inflater = null;
}
super.endOfEntryReached(inputStream);
}
@Override
public void pushBackInputStreamIfNecessary(PushbackInputStream pushbackInputStream) throws IOException {
int n = inflater.getRemaining();
if (n > 0) {
byte[] rawDataCache = getLastReadRawDataCache();
pushbackInputStream.unread(rawDataCache, len - n, n);
}
}
@Override
public void close() throws IOException {
if (inflater != null) {
inflater.end();
}
super.close();
}
private void fill() throws IOException {
len = super.read(buff, 0, buff.length);
if (len == -1) {
throw new EOFException("Unexpected end of input stream");
}
inflater.setInput(buff, 0, len);
}
}

View File

@@ -0,0 +1,27 @@
package net.lingala.zip4j.io.inputstream;
import net.lingala.zip4j.crypto.Decrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.LocalFileHeader;
import java.io.IOException;
class NoCipherInputStream extends CipherInputStream {
public NoCipherInputStream(ZipEntryInputStream zipEntryInputStream, LocalFileHeader localFileHeader, char[] password) throws IOException, ZipException {
super(zipEntryInputStream, localFileHeader, password);
}
@Override
protected Decrypter initializeDecrypter(LocalFileHeader localFileHeader, char[] password) {
return new NoDecrypter();
}
static class NoDecrypter implements Decrypter {
@Override
public int decryptData(byte[] buff, int start, int len) {
return len;
}
}
}

View File

@@ -0,0 +1,26 @@
package net.lingala.zip4j.io.inputstream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import static net.lingala.zip4j.util.FileUtils.getNextNumberedSplitFileCounterAsExtension;
/**
* A split input stream for zip file split with 7-zip. They end with .zip.001, .zip.002, etc
*/
public class NumberedSplitInputStream extends SplitInputStream {
public NumberedSplitInputStream(File zipFile, boolean isSplitZipArchive, int lastSplitZipFileNumber)
throws FileNotFoundException {
super(zipFile, isSplitZipArchive, lastSplitZipFileNumber);
}
@Override
protected File getNextSplitFile(int zipFileIndex) throws IOException {
String currZipFileNameWithPath = zipFile.getCanonicalPath();
String fileNameWithPathAndWithoutExtension = currZipFileNameWithPath.substring(0,
currZipFileNameWithPath.lastIndexOf("."));
return new File(fileNameWithPathAndWithoutExtension + getNextNumberedSplitFileCounterAsExtension(zipFileIndex));
}
}

View File

@@ -0,0 +1,163 @@
package net.lingala.zip4j.io.inputstream;
import net.lingala.zip4j.model.enums.RandomAccessFileMode;
import net.lingala.zip4j.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* A RandomAccessFile which reads files split with 7-zip format (.z001, .z002, etc) as a single file making it easier
* for calling methods to deal with opening appropriate split file to read
*/
public class NumberedSplitRandomAccessFile extends RandomAccessFile {
private long splitLength;
private File[] allSortedSplitFiles;
private RandomAccessFile randomAccessFile;
private byte[] singleByteBuffer = new byte[1];
private int currentOpenSplitFileCounter = 0;
private String rwMode;
public NumberedSplitRandomAccessFile(String name, String mode) throws IOException {
this(new File(name), mode);
}
public NumberedSplitRandomAccessFile(File file, String mode) throws IOException {
this(file, mode, FileUtils.getAllSortedNumberedSplitFiles(file));
}
public NumberedSplitRandomAccessFile(File file, String mode, File[] allSortedSplitFiles) throws IOException {
super(file, mode);
// A (dirty) hack to be able to open any split file without relying on the Parent class
// For that the parent handle is closed, and a new RandomAccessFile is initiated
// This makes it possible for this class to still extend RandomAccessFile and be able to open any split file
// without calling code having to worry about opening a split file. Code using this class can read the
// split files as if it were one file. zip4j uses RandomAccessFile in a lot of places. This hack allows to deal
// with split files with RandomAccessFile without having to modify a lot of code, especially for a feature
// that will not be used most often
super.close();
if (RandomAccessFileMode.WRITE.getValue().equals(mode)) {
throw new IllegalArgumentException("write mode is not allowed for NumberedSplitRandomAccessFile");
}
assertAllSplitFilesExist(allSortedSplitFiles);
this.randomAccessFile = new RandomAccessFile(file, mode);
this.allSortedSplitFiles = allSortedSplitFiles;
this.splitLength = file.length();
this.rwMode = mode;
}
@Override
public int read() throws IOException {
int readLen = read(singleByteBuffer);
if (readLen == -1) {
return -1;
}
return singleByteBuffer[0] & 0xff;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int readLen = randomAccessFile.read(b, off, len);
if (readLen == -1) {
if (currentOpenSplitFileCounter == allSortedSplitFiles.length - 1) {
return -1;
}
openRandomAccessFileForIndex(currentOpenSplitFileCounter + 1);
return read(b, off, len);
}
return readLen;
}
@Override
public void write(int b) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void seek(long pos) throws IOException {
int splitPartOfPosition = (int) (pos / splitLength);
if (splitPartOfPosition != currentOpenSplitFileCounter) {
openRandomAccessFileForIndex(splitPartOfPosition);
}
randomAccessFile.seek(pos - (splitPartOfPosition * splitLength));
}
@Override
public long getFilePointer() throws IOException {
return randomAccessFile.getFilePointer();
}
@Override
public long length() throws IOException {
return randomAccessFile.length();
}
public void seekInCurrentPart(long pos) throws IOException {
randomAccessFile.seek(pos);
}
public void openLastSplitFileForReading() throws IOException {
openRandomAccessFileForIndex(allSortedSplitFiles.length - 1);
}
private void openRandomAccessFileForIndex(int splitCounter) throws IOException {
if (currentOpenSplitFileCounter == splitCounter) {
return;
}
if (splitCounter > allSortedSplitFiles.length - 1) {
throw new IOException("split counter greater than number of split files");
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
randomAccessFile = new RandomAccessFile(allSortedSplitFiles[splitCounter], rwMode);
currentOpenSplitFileCounter = splitCounter;
}
private void assertAllSplitFilesExist(File[] allSortedSplitFiles) throws IOException {
int splitCounter = 1;
for (File splitFile : allSortedSplitFiles) {
String fileExtension = FileUtils.getFileExtension(splitFile);
try {
if (splitCounter != Integer.parseInt(fileExtension)) {
throw new IOException("Split file number " + splitCounter + " does not exist");
}
splitCounter++;
} catch (NumberFormatException e) {
throw new IOException("Split file extension not in expected format. Found: " + fileExtension
+ " expected of format: .001, .002, etc");
}
}
}
}

View File

@@ -0,0 +1,90 @@
package net.lingala.zip4j.io.inputstream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.enums.RandomAccessFileMode;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
public abstract class SplitInputStream extends InputStream {
protected RandomAccessFile randomAccessFile;
protected File zipFile;
private boolean isSplitZipArchive;
private int currentSplitFileCounter = 0;
private byte[] singleByteArray = new byte[1];
public SplitInputStream(File zipFile, boolean isSplitZipArchive, int lastSplitZipFileNumber) throws FileNotFoundException {
this.randomAccessFile = new RandomAccessFile(zipFile, RandomAccessFileMode.READ.getValue());
this.zipFile = zipFile;
this.isSplitZipArchive = isSplitZipArchive;
if (isSplitZipArchive) {
currentSplitFileCounter = lastSplitZipFileNumber;
}
}
@Override
public int read() throws IOException {
int readLen = read(singleByteArray);
if (readLen == -1) {
return -1;
}
return singleByteArray[0];
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int readLen = randomAccessFile.read(b, off, len);
if ((readLen != len || readLen == -1) && isSplitZipArchive) {
openRandomAccessFileForIndex(currentSplitFileCounter + 1);
currentSplitFileCounter++;
if (readLen < 0) readLen = 0;
int newlyRead = randomAccessFile.read(b, readLen, len - readLen);
if (newlyRead > 0) readLen += newlyRead;
}
return readLen;
}
public void prepareExtractionForFileHeader(FileHeader fileHeader) throws IOException {
if (isSplitZipArchive && (currentSplitFileCounter != fileHeader.getDiskNumberStart())) {
openRandomAccessFileForIndex(fileHeader.getDiskNumberStart());
currentSplitFileCounter = fileHeader.getDiskNumberStart();
}
randomAccessFile.seek(fileHeader.getOffsetLocalHeader());
}
protected void openRandomAccessFileForIndex(int zipFileIndex) throws IOException {
File nextSplitFile = getNextSplitFile(zipFileIndex);
if (!nextSplitFile.exists()) {
throw new FileNotFoundException("zip split file does not exist: " + nextSplitFile);
}
randomAccessFile.close();
randomAccessFile = new RandomAccessFile(nextSplitFile, RandomAccessFileMode.READ.getValue());
}
protected abstract File getNextSplitFile(int zipFileIndex) throws IOException;
@Override
public void close() throws IOException {
if (randomAccessFile != null) {
randomAccessFile.close();
}
}
}

View File

@@ -0,0 +1,8 @@
package net.lingala.zip4j.io.inputstream;
class StoreInputStream extends DecompressedInputStream {
public StoreInputStream(CipherInputStream cipherInputStream) {
super(cipherInputStream);
}
}

View File

@@ -0,0 +1,99 @@
package net.lingala.zip4j.io.inputstream;
import java.io.IOException;
import java.io.InputStream;
class ZipEntryInputStream extends InputStream {
private static final int MAX_RAW_READ_FULLY_RETRY_ATTEMPTS = 15;
private InputStream inputStream;
private long numberOfBytesRead = 0;
private byte[] singleByteArray = new byte[1];
private long compressedSize;
public ZipEntryInputStream(InputStream inputStream, long compressedSize) {
this.inputStream = inputStream;
this.compressedSize = compressedSize;
}
@Override
public int read() throws IOException {
int readLen = read(singleByteArray);
if (readLen == -1) {
return -1;
}
return singleByteArray[0];
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (compressedSize != -1) {
if (numberOfBytesRead >= compressedSize) {
return -1;
}
if (len > compressedSize - numberOfBytesRead) {
len = (int) (compressedSize - numberOfBytesRead);
}
}
int readLen = inputStream.read(b, off, len);
if (readLen > 0) {
numberOfBytesRead += readLen;
}
return readLen;
}
public int readRawFully(byte[] b) throws IOException {
int readLen = inputStream.read(b);
if (readLen != b.length) {
readLen = readUntilBufferIsFull(b, readLen);
if (readLen != b.length) {
throw new IOException("Cannot read fully into byte buffer");
}
}
return readLen;
}
private int readUntilBufferIsFull(byte[] b, int readLength) throws IOException {
int remainingLength = b.length - readLength;
int loopReadLength = 0;
int retryAttempt = 0;
while (readLength < b.length && loopReadLength != -1 && retryAttempt < MAX_RAW_READ_FULLY_RETRY_ATTEMPTS) {
loopReadLength += inputStream.read(b, readLength, remainingLength);
if (loopReadLength > 0) {
readLength += loopReadLength;
remainingLength -= loopReadLength;
}
retryAttempt++;
}
return readLength;
}
@Override
public void close() throws IOException {
inputStream.close();
}
public long getNumberOfBytesRead() {
return numberOfBytesRead;
}
}

View File

@@ -0,0 +1,320 @@
/*
* 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 net.lingala.zip4j.io.inputstream;
import net.lingala.zip4j.crypto.AESDecrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderReader;
import net.lingala.zip4j.headers.HeaderSignature;
import net.lingala.zip4j.model.DataDescriptor;
import net.lingala.zip4j.model.ExtraDataRecord;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.enums.AesVersion;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.util.InternalZipConstants;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.nio.charset.Charset;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import static net.lingala.zip4j.util.Zip4jUtil.getCompressionMethod;
public class ZipInputStream extends InputStream {
private PushbackInputStream inputStream;
private DecompressedInputStream decompressedInputStream;
private HeaderReader headerReader = new HeaderReader();
private char[] password;
private LocalFileHeader localFileHeader;
private CRC32 crc32 = new CRC32();
private byte[] endOfEntryBuffer;
private boolean canSkipExtendedLocalFileHeader = false;
private Charset charset;
public ZipInputStream(InputStream inputStream) {
this(inputStream, null, InternalZipConstants.CHARSET_UTF_8);
}
public ZipInputStream(InputStream inputStream, Charset charset) {
this(inputStream, null, charset);
}
public ZipInputStream(InputStream inputStream, char[] password) {
this(inputStream, password, InternalZipConstants.CHARSET_UTF_8);
}
public ZipInputStream(InputStream inputStream, char[] password, Charset charset) {
if(charset == null) {
charset = InternalZipConstants.CHARSET_UTF_8;
}
this.inputStream = new PushbackInputStream(inputStream, InternalZipConstants.BUFF_SIZE);
this.password = password;
this.charset = charset;
}
public LocalFileHeader getNextEntry() throws IOException {
return getNextEntry(null);
}
public LocalFileHeader getNextEntry(FileHeader fileHeader) throws IOException {
if (localFileHeader != null) {
readUntilEndOfEntry();
}
localFileHeader = headerReader.readLocalFileHeader(inputStream, charset);
if (localFileHeader == null) {
return null;
}
verifyLocalFileHeader(localFileHeader);
crc32.reset();
if (fileHeader != null) {
localFileHeader.setCrc(fileHeader.getCrc());
localFileHeader.setCompressedSize(fileHeader.getCompressedSize());
localFileHeader.setUncompressedSize(fileHeader.getUncompressedSize());
canSkipExtendedLocalFileHeader = true;
} else {
canSkipExtendedLocalFileHeader = false;
}
this.decompressedInputStream = initializeEntryInputStream(localFileHeader);
return localFileHeader;
}
@Override
public int read() throws IOException {
byte[] b = new byte[1];
int readLen = read(b);
if (readLen == -1) {
return -1;
}
return b[0] & 0xff;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (len < 0) {
throw new IllegalArgumentException("Negative read length");
}
if (len == 0) {
return 0;
}
if (localFileHeader == null) {
// localfileheader can be null when end of compressed data is reached. If null check is missing, read method will
// throw a NPE when end of compressed data is reached and read is called again.
return -1;
}
try {
int readLen = decompressedInputStream.read(b, off, len);
if (readLen == -1) {
endOfCompressedDataReached();
} else {
crc32.update(b, off, readLen);
}
return readLen;
} catch (IOException e) {
if (e.getCause() != null && e.getCause() instanceof DataFormatException
&& isEncryptionMethodZipStandard(localFileHeader)) {
throw new ZipException(e.getMessage(), e.getCause(), ZipException.Type.WRONG_PASSWORD);
}
throw e;
}
}
@Override
public void close() throws IOException {
if (decompressedInputStream != null) {
decompressedInputStream.close();
}
}
public int getAvailableBytesInPushBackInputStream() throws IOException {
return inputStream.available();
}
private void endOfCompressedDataReached() throws IOException {
//With inflater, without knowing the compressed or uncompressed size, we over read necessary data
//In such cases, we have to push back the inputstream to the end of data
decompressedInputStream.pushBackInputStreamIfNecessary(inputStream);
//First signal the end of data for this entry so that ciphers can read any header data if applicable
decompressedInputStream.endOfEntryReached(inputStream);
readExtendedLocalFileHeaderIfPresent();
verifyCrc();
resetFields();
}
private DecompressedInputStream initializeEntryInputStream(LocalFileHeader localFileHeader) throws IOException {
ZipEntryInputStream zipEntryInputStream = new ZipEntryInputStream(inputStream, getCompressedSize(localFileHeader));
CipherInputStream cipherInputStream = initializeCipherInputStream(zipEntryInputStream, localFileHeader);
return initializeDecompressorForThisEntry(cipherInputStream, localFileHeader);
}
private CipherInputStream initializeCipherInputStream(ZipEntryInputStream zipEntryInputStream, LocalFileHeader localFileHeader) throws IOException {
if (!localFileHeader.isEncrypted()) {
return new NoCipherInputStream(zipEntryInputStream, localFileHeader, password);
}
if (localFileHeader.getEncryptionMethod() == EncryptionMethod.AES) {
return new AesCipherInputStream(zipEntryInputStream, localFileHeader, password);
} else if (localFileHeader.getEncryptionMethod() == EncryptionMethod.ZIP_STANDARD) {
return new ZipStandardCipherInputStream(zipEntryInputStream, localFileHeader, password);
} else {
final String message = String.format("Entry [%s] Strong Encryption not supported", localFileHeader.getFileName());
throw new ZipException(message, ZipException.Type.UNSUPPORTED_ENCRYPTION);
}
}
private DecompressedInputStream initializeDecompressorForThisEntry(CipherInputStream cipherInputStream, LocalFileHeader localFileHeader) {
CompressionMethod compressionMethod = getCompressionMethod(localFileHeader);
if (compressionMethod == CompressionMethod.DEFLATE) {
return new InflaterInputStream(cipherInputStream);
}
return new StoreInputStream(cipherInputStream);
}
private void readExtendedLocalFileHeaderIfPresent() throws IOException {
if (!localFileHeader.isDataDescriptorExists() || canSkipExtendedLocalFileHeader) {
return;
}
DataDescriptor dataDescriptor = headerReader.readDataDescriptor(inputStream,
checkIfZip64ExtraDataRecordPresentInLFH(localFileHeader.getExtraDataRecords()));
localFileHeader.setCompressedSize(dataDescriptor.getCompressedSize());
localFileHeader.setUncompressedSize(dataDescriptor.getUncompressedSize());
localFileHeader.setCrc(dataDescriptor.getCrc());
}
private void verifyLocalFileHeader(LocalFileHeader localFileHeader) throws IOException {
if (!isEntryDirectory(localFileHeader.getFileName())
&& localFileHeader.getCompressionMethod() == CompressionMethod.STORE
&& localFileHeader.getUncompressedSize() < 0) {
throw new IOException("Invalid local file header for: " + localFileHeader.getFileName()
+ ". Uncompressed size has to be set for entry of compression type store which is not a directory");
}
}
private boolean checkIfZip64ExtraDataRecordPresentInLFH(List<ExtraDataRecord> extraDataRecords) {
if (extraDataRecords == null) {
return false;
}
for (ExtraDataRecord extraDataRecord : extraDataRecords) {
if (extraDataRecord.getHeader() == HeaderSignature.ZIP64_EXTRA_FIELD_SIGNATURE.getValue()) {
return true;
}
}
return false;
}
private void verifyCrc() throws IOException {
if (localFileHeader.getEncryptionMethod() == EncryptionMethod.AES
&& localFileHeader.getAesExtraDataRecord().getAesVersion().equals(AesVersion.TWO)) {
// Verification will be done in this case by AesCipherInputStream
return;
}
if (localFileHeader.getCrc() != crc32.getValue()) {
ZipException.Type exceptionType = ZipException.Type.CHECKSUM_MISMATCH;
if (isEncryptionMethodZipStandard(localFileHeader)) {
exceptionType = ZipException.Type.WRONG_PASSWORD;
}
throw new ZipException("Reached end of entry, but crc verification failed for " + localFileHeader.getFileName(),
exceptionType);
}
}
private void resetFields() {
localFileHeader = null;
crc32.reset();
}
private boolean isEntryDirectory(String entryName) {
return entryName.endsWith("/") || entryName.endsWith("\\");
}
private long getCompressedSize(LocalFileHeader localFileHeader) {
if (getCompressionMethod(localFileHeader).equals(CompressionMethod.STORE)) {
return localFileHeader.getUncompressedSize();
}
if (localFileHeader.isDataDescriptorExists() && !canSkipExtendedLocalFileHeader) {
return -1;
}
return localFileHeader.getCompressedSize() - getEncryptionHeaderSize(localFileHeader);
}
private int getEncryptionHeaderSize(LocalFileHeader localFileHeader) {
if (!localFileHeader.isEncrypted()) {
return 0;
}
if (localFileHeader.getEncryptionMethod().equals(EncryptionMethod.AES)) {
return InternalZipConstants.AES_AUTH_LENGTH + InternalZipConstants.AES_PASSWORD_VERIFIER_LENGTH
+ localFileHeader.getAesExtraDataRecord().getAesKeyStrength().getSaltLength();
} else if (localFileHeader.getEncryptionMethod().equals(EncryptionMethod.ZIP_STANDARD)) {
return InternalZipConstants.STD_DEC_HDR_SIZE;
} else {
return 0;
}
}
private void readUntilEndOfEntry() throws IOException {
if (localFileHeader.isDirectory() || localFileHeader.getCompressedSize() == 0) {
return;
}
if (endOfEntryBuffer == null) {
endOfEntryBuffer = new byte[512];
}
while (read(endOfEntryBuffer) != -1);
}
private boolean isEncryptionMethodZipStandard(LocalFileHeader localFileHeader) {
return localFileHeader.isEncrypted() && EncryptionMethod.ZIP_STANDARD.equals(localFileHeader.getEncryptionMethod());
}
}

View File

@@ -0,0 +1,27 @@
package net.lingala.zip4j.io.inputstream;
import net.lingala.zip4j.crypto.StandardDecrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.LocalFileHeader;
import java.io.IOException;
import static net.lingala.zip4j.util.InternalZipConstants.STD_DEC_HDR_SIZE;
class ZipStandardCipherInputStream extends CipherInputStream<StandardDecrypter> {
public ZipStandardCipherInputStream(ZipEntryInputStream zipEntryInputStream, LocalFileHeader localFileHeader, char[] password) throws IOException, ZipException {
super(zipEntryInputStream, localFileHeader, password);
}
@Override
protected StandardDecrypter initializeDecrypter(LocalFileHeader localFileHeader, char[] password) throws ZipException, IOException {
return new StandardDecrypter(password, localFileHeader.getCrcRawData(), getStandardDecrypterHeaderBytes());
}
private byte[] getStandardDecrypterHeaderBytes() throws IOException {
byte[] headerBytes = new byte[STD_DEC_HDR_SIZE];
readRaw(headerBytes);
return headerBytes;
}
}

View File

@@ -0,0 +1,34 @@
package net.lingala.zip4j.io.inputstream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* A split input stream for zip file split as per zip specification. They end with .z01, .z02... .zip
*/
public class ZipStandardSplitInputStream extends SplitInputStream {
private int lastSplitZipFileNumber;
public ZipStandardSplitInputStream(File zipFile, boolean isSplitZipArchive, int lastSplitZipFileNumber) throws FileNotFoundException {
super(zipFile, isSplitZipArchive, lastSplitZipFileNumber);
this.lastSplitZipFileNumber = lastSplitZipFileNumber;
}
@Override
protected File getNextSplitFile(int zipFileIndex) throws IOException {
if (zipFileIndex == lastSplitZipFileNumber) {
return zipFile;
}
String currZipFileNameWithPath = zipFile.getCanonicalPath();
String extensionSubString = ".z0";
if (zipFileIndex >= 9) {
extensionSubString = ".z";
}
return new File(currZipFileNameWithPath.substring(0,
currZipFileNameWithPath.lastIndexOf(".")) + extensionSubString + (zipFileIndex + 1));
}
}

View File

@@ -0,0 +1,76 @@
package net.lingala.zip4j.io.outputstream;
import net.lingala.zip4j.crypto.AESEncrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import java.io.IOException;
import java.io.OutputStream;
import static net.lingala.zip4j.util.InternalZipConstants.AES_BLOCK_SIZE;
class AesCipherOutputStream extends CipherOutputStream<AESEncrypter> {
private byte[] pendingBuffer = new byte[AES_BLOCK_SIZE];
private int pendingBufferLength = 0;
public AesCipherOutputStream(ZipEntryOutputStream outputStream, ZipParameters zipParameters, char[] password) throws IOException, ZipException {
super(outputStream, zipParameters, password);
}
@Override
protected AESEncrypter initializeEncrypter(OutputStream outputStream, ZipParameters zipParameters, char[] password) throws IOException, ZipException {
AESEncrypter encrypter = new AESEncrypter(password, zipParameters.getAesKeyStrength());
writeAesEncryptionHeaderData(encrypter);
return encrypter;
}
private void writeAesEncryptionHeaderData(AESEncrypter encrypter) throws IOException {
writeHeaders(encrypter.getSaltBytes());
writeHeaders(encrypter.getDerivedPasswordVerifier());
}
@Override
public void write(int b) throws IOException {
write(new byte[] {(byte) b});
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (len >= (AES_BLOCK_SIZE - pendingBufferLength)) {
System.arraycopy(b, off, pendingBuffer, pendingBufferLength, (AES_BLOCK_SIZE - pendingBufferLength));
super.write(pendingBuffer, 0, pendingBuffer.length);
off = (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;
}
super.write(b, off, len);
}
@Override
public void closeEntry() throws IOException {
if (this.pendingBufferLength != 0) {
super.write(pendingBuffer, 0, pendingBufferLength);
pendingBufferLength = 0;
}
writeHeaders(getEncrypter().getFinalMac());
super.closeEntry();
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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 net.lingala.zip4j.io.outputstream;
import net.lingala.zip4j.crypto.Encrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import java.io.IOException;
import java.io.OutputStream;
abstract class CipherOutputStream<T extends Encrypter> extends OutputStream {
private ZipEntryOutputStream zipEntryOutputStream;
private T encrypter;
public CipherOutputStream(ZipEntryOutputStream zipEntryOutputStream, ZipParameters zipParameters, char[] password)
throws IOException, ZipException {
this.zipEntryOutputStream = zipEntryOutputStream;
this.encrypter = initializeEncrypter(zipEntryOutputStream, zipParameters, password);
}
@Override
public void write(int b) throws IOException {
zipEntryOutputStream.write(b);
}
@Override
public void write(byte[] b) throws IOException {
zipEntryOutputStream.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
encrypter.encryptData(b, off, len);
zipEntryOutputStream.write(b, off, len);
}
public void writeHeaders(byte[] b) throws IOException {
zipEntryOutputStream.write(b);
}
public void closeEntry() throws IOException {
zipEntryOutputStream.closeEntry();
}
@Override
public void close() throws IOException {
zipEntryOutputStream.close();
}
public long getNumberOfBytesWrittenForThisEntry() {
return zipEntryOutputStream.getNumberOfBytesWrittenForThisEntry();
}
protected T getEncrypter() {
return encrypter;
}
protected abstract T initializeEncrypter(OutputStream outputStream, ZipParameters zipParameters, char[] password)
throws IOException, ZipException;
}

View File

@@ -0,0 +1,41 @@
package net.lingala.zip4j.io.outputstream;
import java.io.IOException;
import java.io.OutputStream;
abstract class CompressedOutputStream extends OutputStream {
private CipherOutputStream cipherOutputStream;
public CompressedOutputStream(CipherOutputStream cipherOutputStream) {
this.cipherOutputStream = cipherOutputStream;
}
@Override
public void write(int b) throws IOException {
write(new byte[] {(byte) b});
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
cipherOutputStream.write(b, off, len);
}
protected void closeEntry() throws IOException {
cipherOutputStream.closeEntry();
}
@Override
public void close() throws IOException {
cipherOutputStream.close();
}
public long getCompressedSize() {
return cipherOutputStream.getNumberOfBytesWrittenForThisEntry();
}
}

View File

@@ -0,0 +1,92 @@
package net.lingala.zip4j.io.outputstream;
import net.lingala.zip4j.exception.ZipException;
import java.io.IOException;
import java.io.OutputStream;
public class CountingOutputStream extends OutputStream implements OutputStreamWithSplitZipSupport {
private OutputStream outputStream;
private long numberOfBytesWritten = 0;
public CountingOutputStream(OutputStream outputStream) {
this.outputStream = outputStream;
}
@Override
public void write(int b) throws IOException {
write(new byte[] {(byte) b});
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
outputStream.write(b, off, len);
numberOfBytesWritten += len;
}
@Override
public int getCurrentSplitFileCounter() {
if (isSplitZipFile()) {
return ((SplitOutputStream) outputStream).getCurrentSplitFileCounter();
}
return 0;
}
public long getOffsetForNextEntry() throws IOException {
if (outputStream instanceof SplitOutputStream) {
return ((SplitOutputStream) outputStream).getFilePointer();
}
return numberOfBytesWritten;
}
public long getSplitLength() {
if (isSplitZipFile()) {
return ((SplitOutputStream) outputStream).getSplitLength();
}
return 0;
}
public boolean isSplitZipFile() {
return outputStream instanceof SplitOutputStream
&& ((SplitOutputStream)outputStream).isSplitZipFile();
}
public long getNumberOfBytesWritten() throws IOException {
if (outputStream instanceof SplitOutputStream) {
return ((SplitOutputStream) outputStream).getFilePointer();
}
return numberOfBytesWritten;
}
public boolean checkBuffSizeAndStartNextSplitFile(int bufferSize) throws ZipException {
if (!isSplitZipFile()) {
return false;
}
return ((SplitOutputStream)outputStream).checkBufferSizeAndStartNextSplitFile(bufferSize);
}
@Override
public long getFilePointer() throws IOException {
if (outputStream instanceof SplitOutputStream) {
return ((SplitOutputStream) outputStream).getFilePointer();
}
return numberOfBytesWritten;
}
@Override
public void close() throws IOException {
outputStream.close();
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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 net.lingala.zip4j.io.outputstream;
import net.lingala.zip4j.model.enums.CompressionLevel;
import java.io.IOException;
import java.util.zip.Deflater;
import static net.lingala.zip4j.util.InternalZipConstants.BUFF_SIZE;
class DeflaterOutputStream extends CompressedOutputStream {
private byte[] buff = new byte[BUFF_SIZE];
protected Deflater deflater;
public DeflaterOutputStream(CipherOutputStream cipherOutputStream, CompressionLevel compressionLevel) {
super(cipherOutputStream);
deflater = new Deflater(compressionLevel.getLevel(), true);
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
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 {
deflater.setInput(buf, off, len);
while (!deflater.needsInput()) {
deflate();
}
}
private void deflate() throws IOException {
int len = deflater.deflate(buff, 0, buff.length);
if (len > 0) {
super.write(buff, 0, len);
}
}
public void closeEntry() throws IOException {
if (!deflater.finished()) {
deflater.finish();
while (!deflater.finished()) {
deflate();
}
}
deflater.end();
super.closeEntry();
}
}

View File

@@ -0,0 +1,33 @@
package net.lingala.zip4j.io.outputstream;
import net.lingala.zip4j.crypto.Encrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import java.io.IOException;
import java.io.OutputStream;
class NoCipherOutputStream extends CipherOutputStream<NoCipherOutputStream.NoEncrypter> {
public NoCipherOutputStream(ZipEntryOutputStream zipEntryOutputStream, ZipParameters zipParameters, char[] password) throws IOException, ZipException {
super(zipEntryOutputStream, zipParameters, password);
}
@Override
protected NoEncrypter initializeEncrypter(OutputStream outputStream, ZipParameters zipParameters, char[] password) {
return new NoEncrypter();
}
static class NoEncrypter implements Encrypter {
@Override
public int encryptData(byte[] buff) {
return encryptData(buff, 0, buff.length);
}
@Override
public int encryptData(byte[] buff, int start, int len) {
return len;
}
}
}

View File

@@ -0,0 +1,10 @@
package net.lingala.zip4j.io.outputstream;
import java.io.IOException;
public interface OutputStreamWithSplitZipSupport {
long getFilePointer() throws IOException;
int getCurrentSplitFileCounter();
}

View File

@@ -0,0 +1,212 @@
/*
* 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 net.lingala.zip4j.io.outputstream;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderSignature;
import net.lingala.zip4j.model.enums.RandomAccessFileMode;
import net.lingala.zip4j.util.RawIO;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import static net.lingala.zip4j.util.FileUtils.getZipFileNameWithoutExtension;
import static net.lingala.zip4j.util.InternalZipConstants.MIN_SPLIT_LENGTH;
public class SplitOutputStream extends OutputStream implements OutputStreamWithSplitZipSupport {
private RandomAccessFile raf;
private long splitLength;
private File zipFile;
private int currSplitFileCounter;
private long bytesWrittenForThisPart;
private RawIO rawIO = new RawIO();
public SplitOutputStream(File file) throws FileNotFoundException, ZipException {
this(file, -1);
}
public SplitOutputStream(File file, long splitLength) throws FileNotFoundException, ZipException {
if (splitLength >= 0 && splitLength < MIN_SPLIT_LENGTH) {
throw new ZipException("split length less than minimum allowed split length of " + MIN_SPLIT_LENGTH + " Bytes");
}
this.raf = new RandomAccessFile(file, RandomAccessFileMode.WRITE.getValue());
this.splitLength = splitLength;
this.zipFile = file;
this.currSplitFileCounter = 0;
this.bytesWrittenForThisPart = 0;
}
public void write(int b) throws IOException {
write(new byte[] {(byte) b});
}
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) {
raf.write(b, off, len);
bytesWrittenForThisPart += len;
return;
}
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;
}
}
private void startNextSplitFile() throws IOException {
String zipFileWithoutExt = getZipFileNameWithoutExtension(zipFile.getName());
String zipFileName = zipFile.getAbsolutePath();
String parentPath = (zipFile.getParent() == null) ? "" : zipFile.getParent()
+ System.getProperty("file.separator");
String fileExtension = ".z0" + (currSplitFileCounter + 1);
if (currSplitFileCounter >= 9) {
fileExtension = ".z" + (currSplitFileCounter + 1);
}
File currSplitFile = new File(parentPath + zipFileWithoutExt + fileExtension);
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, RandomAccessFileMode.WRITE.getValue());
currSplitFileCounter++;
}
private boolean isHeaderData(byte[] buff) {
int signature = rawIO.readIntLittleEndian(buff);
for (HeaderSignature headerSignature : HeaderSignature.values()) {
//Ignore split signature
if (headerSignature != HeaderSignature.SPLIT_ZIP &&
headerSignature.getValue() == 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 checkBufferSizeAndStartNextSplitFile(int bufferSize) throws ZipException {
if (bufferSize < 0) {
throw new ZipException("negative buffersize for checkBufferSizeAndStartNextSplitFile");
}
if (!isBufferSizeFitForCurrSplitFile(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.
*/
private boolean isBufferSizeFitForCurrSplitFile(int bufferSize) {
if (splitLength >= 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 int skipBytes(int n) throws IOException {
return raf.skipBytes(n);
}
public void close() throws IOException {
raf.close();
}
@Override
public long getFilePointer() throws IOException {
return raf.getFilePointer();
}
public boolean isSplitZipFile() {
return splitLength != -1;
}
public long getSplitLength() {
return splitLength;
}
@Override
public int getCurrentSplitFileCounter() {
return currSplitFileCounter;
}
}

View File

@@ -0,0 +1,9 @@
package net.lingala.zip4j.io.outputstream;
class StoreOutputStream extends CompressedOutputStream {
public StoreOutputStream(CipherOutputStream cipherOutputStream) {
super(cipherOutputStream);
}
}

View File

@@ -0,0 +1,50 @@
package net.lingala.zip4j.io.outputstream;
import java.io.IOException;
import java.io.OutputStream;
class ZipEntryOutputStream extends OutputStream {
private long numberOfBytesWrittenForThisEntry = 0;
private OutputStream outputStream;
private boolean entryClosed;
public ZipEntryOutputStream(OutputStream outputStream) {
this.outputStream = outputStream;
entryClosed = false;
}
@Override
public void write(int b) throws IOException {
write(new byte[] {(byte) b});
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (entryClosed) {
throw new IllegalStateException("ZipEntryOutputStream is closed");
}
outputStream.write(b, off, len);
numberOfBytesWrittenForThisEntry += len;
}
public void closeEntry() throws IOException {
entryClosed = true;
}
public long getNumberOfBytesWrittenForThisEntry() {
return numberOfBytesWrittenForThisEntry;
}
@Override
public void close() throws IOException {
// Do nothing
// Do not close the outputstream yet. This will be closed by countingOutputStream
}
}

View File

@@ -0,0 +1,228 @@
package net.lingala.zip4j.io.outputstream;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.FileHeaderFactory;
import net.lingala.zip4j.headers.HeaderSignature;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.AesVersion;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.RawIO;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.zip.CRC32;
public class ZipOutputStream extends OutputStream {
private CountingOutputStream countingOutputStream;
private char[] password;
private ZipModel zipModel;
private CompressedOutputStream compressedOutputStream;
private FileHeader fileHeader;
private LocalFileHeader localFileHeader;
private FileHeaderFactory fileHeaderFactory = new FileHeaderFactory();
private HeaderWriter headerWriter = new HeaderWriter();
private CRC32 crc32 = new CRC32();
private RawIO rawIO = new RawIO();
private long uncompressedSizeForThisEntry = 0;
private Charset charset;
private boolean streamClosed;
public ZipOutputStream(OutputStream outputStream) throws IOException {
this(outputStream, null, InternalZipConstants.CHARSET_UTF_8);
}
public ZipOutputStream(OutputStream outputStream, Charset charset) throws IOException {
this(outputStream, null, charset);
}
public ZipOutputStream(OutputStream outputStream, char[] password) throws IOException {
this(outputStream, password, InternalZipConstants.CHARSET_UTF_8);
}
public ZipOutputStream(OutputStream outputStream, char[] password, Charset charset) throws IOException {
this(outputStream, password, charset, new ZipModel());
}
public ZipOutputStream(OutputStream outputStream, char[] password, Charset charset, ZipModel zipModel) throws IOException {
if(charset == null) {
charset = InternalZipConstants.CHARSET_UTF_8;
}
this.countingOutputStream = new CountingOutputStream(outputStream);
this.password = password;
this.charset = charset;
this.zipModel = initializeZipModel(zipModel, countingOutputStream);
this.streamClosed = false;
writeSplitZipHeaderIfApplicable();
}
public void putNextEntry(ZipParameters zipParameters) throws IOException {
verifyZipParameters(zipParameters);
initializeAndWriteFileHeader(zipParameters);
//Initialisation of below compressedOutputStream should happen after writing local file header
//because local header data should be written first and then the encryption header data
//and below initialisation writes encryption header data
compressedOutputStream = initializeCompressedOutputStream(zipParameters);
}
public void write(int b) throws IOException {
write(new byte[] {(byte)b});
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
ensureStreamOpen();
crc32.update(b, off, len);
compressedOutputStream.write(b, off, len);
uncompressedSizeForThisEntry += len;
}
public FileHeader closeEntry() throws IOException {
compressedOutputStream.closeEntry();
long compressedSize = compressedOutputStream.getCompressedSize();
fileHeader.setCompressedSize(compressedSize);
localFileHeader.setCompressedSize(compressedSize);
fileHeader.setUncompressedSize(uncompressedSizeForThisEntry);
localFileHeader.setUncompressedSize(uncompressedSizeForThisEntry);
if (writeCrc(fileHeader)) {
fileHeader.setCrc(crc32.getValue());
localFileHeader.setCrc(crc32.getValue());
}
zipModel.getLocalFileHeaders().add(localFileHeader);
zipModel.getCentralDirectory().getFileHeaders().add(fileHeader);
if (localFileHeader.isDataDescriptorExists()) {
headerWriter.writeExtendedLocalHeader(localFileHeader, countingOutputStream);
}
reset();
return fileHeader;
}
@Override
public void close() throws IOException {
zipModel.getEndOfCentralDirectoryRecord().setOffsetOfStartOfCentralDirectory(countingOutputStream.getNumberOfBytesWritten());
headerWriter.finalizeZipFile(zipModel, countingOutputStream, charset);
countingOutputStream.close();
this.streamClosed = true;
}
public void setComment(String comment) throws IOException {
ensureStreamOpen();
zipModel.getEndOfCentralDirectoryRecord().setComment(comment);
}
private void ensureStreamOpen() throws IOException {
if (streamClosed) {
throw new IOException("Stream is closed");
}
}
private ZipModel initializeZipModel(ZipModel zipModel, CountingOutputStream countingOutputStream) {
if (zipModel == null) {
zipModel = new ZipModel();
}
if (countingOutputStream.isSplitZipFile()) {
zipModel.setSplitArchive(true);
zipModel.setSplitLength(countingOutputStream.getSplitLength());
}
return zipModel;
}
private void initializeAndWriteFileHeader(ZipParameters zipParameters) throws IOException {
fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, countingOutputStream.isSplitZipFile(),
countingOutputStream.getCurrentSplitFileCounter(), charset, rawIO);
fileHeader.setOffsetLocalHeader(countingOutputStream.getOffsetForNextEntry());
localFileHeader = fileHeaderFactory.generateLocalFileHeader(fileHeader);
headerWriter.writeLocalFileHeader(zipModel, localFileHeader, countingOutputStream, charset);
}
private void reset() throws IOException {
uncompressedSizeForThisEntry = 0;
crc32.reset();
compressedOutputStream.close();
}
private void writeSplitZipHeaderIfApplicable() throws IOException {
if (!countingOutputStream.isSplitZipFile()) {
return;
}
rawIO.writeIntLittleEndian(countingOutputStream, (int) HeaderSignature.SPLIT_ZIP.getValue());
}
private CompressedOutputStream initializeCompressedOutputStream(ZipParameters zipParameters) throws IOException {
ZipEntryOutputStream zipEntryOutputStream = new ZipEntryOutputStream(countingOutputStream);
CipherOutputStream cipherOutputStream = initializeCipherOutputStream(zipEntryOutputStream, zipParameters);
return initializeCompressedOutputStream(cipherOutputStream, zipParameters);
}
private CipherOutputStream initializeCipherOutputStream(ZipEntryOutputStream zipEntryOutputStream,
ZipParameters zipParameters) throws IOException {
if (!zipParameters.isEncryptFiles()) {
return new NoCipherOutputStream(zipEntryOutputStream, zipParameters, null);
}
if (password == null || password.length == 0) {
throw new ZipException("password not set");
}
if (zipParameters.getEncryptionMethod() == EncryptionMethod.AES) {
return new AesCipherOutputStream(zipEntryOutputStream, zipParameters, password);
} else if (zipParameters.getEncryptionMethod() == EncryptionMethod.ZIP_STANDARD) {
return new ZipStandardCipherOutputStream(zipEntryOutputStream, zipParameters, password);
} else {
throw new ZipException("Invalid encryption method");
}
}
private CompressedOutputStream initializeCompressedOutputStream(CipherOutputStream cipherOutputStream,
ZipParameters zipParameters) {
if (zipParameters.getCompressionMethod() == CompressionMethod.DEFLATE) {
return new DeflaterOutputStream(cipherOutputStream, zipParameters.getCompressionLevel());
}
return new StoreOutputStream(cipherOutputStream);
}
private void verifyZipParameters(ZipParameters zipParameters) {
if (zipParameters.getCompressionMethod() == CompressionMethod.STORE
&& zipParameters.getEntrySize() < 0
&& !isEntryDirectory(zipParameters.getFileNameInZip())
&& zipParameters.isWriteExtendedLocalFileHeader()) {
throw new IllegalArgumentException("uncompressed size should be set for zip entries of compression type store");
}
}
private boolean writeCrc(FileHeader fileHeader) {
boolean isAesEncrypted = fileHeader.isEncrypted() && fileHeader.getEncryptionMethod().equals(EncryptionMethod.AES);
if (!isAesEncrypted) {
return true;
}
return fileHeader.getAesExtraDataRecord().getAesVersion().equals(AesVersion.ONE);
}
private boolean isEntryDirectory(String entryName) {
return entryName.endsWith("/") || entryName.endsWith("\\");
}
}

View File

@@ -0,0 +1,48 @@
package net.lingala.zip4j.io.outputstream;
import net.lingala.zip4j.crypto.StandardEncrypter;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jUtil;
import java.io.IOException;
import java.io.OutputStream;
class ZipStandardCipherOutputStream extends CipherOutputStream<StandardEncrypter> {
public ZipStandardCipherOutputStream(ZipEntryOutputStream outputStream, ZipParameters zipParameters, char[] password) throws IOException, ZipException {
super(outputStream, zipParameters, password);
}
@Override
protected StandardEncrypter initializeEncrypter(OutputStream outputStream, ZipParameters zipParameters, char[] password) throws IOException, ZipException {
long key = getEncryptionKey(zipParameters);
StandardEncrypter encrypter = new StandardEncrypter(password, key);
writeHeaders(encrypter.getHeaderBytes());
return encrypter;
}
@Override
public void write(int b) throws IOException {
write(new byte[] {(byte) b});
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
}
private long getEncryptionKey(ZipParameters zipParameters) {
if (zipParameters.isWriteExtendedLocalFileHeader()) {
long dosTime = Zip4jUtil.epochToExtendedDosTime(zipParameters.getLastModifiedFileTime());
return (dosTime & 0x0000ffff) << 16;
}
return zipParameters.getEntryCRC();
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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 net.lingala.zip4j.model;
import net.lingala.zip4j.headers.HeaderSignature;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import net.lingala.zip4j.model.enums.AesVersion;
import net.lingala.zip4j.model.enums.CompressionMethod;
public class AESExtraDataRecord extends ZipHeader {
private int dataSize;
private AesVersion aesVersion;
private String vendorID;
private AesKeyStrength aesKeyStrength;
private CompressionMethod compressionMethod;
public AESExtraDataRecord() {
setSignature(HeaderSignature.AES_EXTRA_DATA_RECORD);
dataSize = 7;
aesVersion = AesVersion.TWO;
vendorID = "AE";
aesKeyStrength = AesKeyStrength.KEY_STRENGTH_256;
compressionMethod = CompressionMethod.DEFLATE;
}
public int getDataSize() {
return dataSize;
}
public void setDataSize(int dataSize) {
this.dataSize = dataSize;
}
public AesVersion getAesVersion() {
return aesVersion;
}
public void setAesVersion(AesVersion aesVersion) {
this.aesVersion = aesVersion;
}
public String getVendorID() {
return vendorID;
}
public void setVendorID(String vendorID) {
this.vendorID = vendorID;
}
public AesKeyStrength getAesKeyStrength() {
return aesKeyStrength;
}
public void setAesKeyStrength(AesKeyStrength aesKeyStrength) {
this.aesKeyStrength = aesKeyStrength;
}
public CompressionMethod getCompressionMethod() {
return compressionMethod;
}
public void setCompressionMethod(CompressionMethod compressionMethod) {
this.compressionMethod = compressionMethod;
}
}

View File

@@ -0,0 +1,199 @@
package net.lingala.zip4j.model;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.util.Zip4jUtil;
import java.util.List;
public abstract class AbstractFileHeader extends ZipHeader {
private int versionNeededToExtract;
private byte[] generalPurposeFlag;
private CompressionMethod compressionMethod;
private long lastModifiedTime;
private long crc = 0;
private byte[] crcRawData;
private long compressedSize = 0;
private long uncompressedSize = 0;
private int fileNameLength;
private int extraFieldLength;
private String fileName;
private boolean isEncrypted;
private EncryptionMethod encryptionMethod = EncryptionMethod.NONE;
private boolean dataDescriptorExists;
private Zip64ExtendedInfo zip64ExtendedInfo;
private AESExtraDataRecord aesExtraDataRecord;
private boolean fileNameUTF8Encoded;
private List<ExtraDataRecord> extraDataRecords;
private boolean isDirectory;
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 CompressionMethod getCompressionMethod() {
return compressionMethod;
}
public void setCompressionMethod(CompressionMethod compressionMethod) {
this.compressionMethod = compressionMethod;
}
public long getLastModifiedTime() {
return lastModifiedTime;
}
public void setLastModifiedTime(long lastModifiedTime) {
this.lastModifiedTime = lastModifiedTime;
}
public long getLastModifiedTimeEpoch() {
return Zip4jUtil.dosToExtendedEpochTme(lastModifiedTime);
}
public long getCrc() {
return crc;
}
public void setCrc(long crc) {
this.crc = crc;
}
public byte[] getCrcRawData() {
return crcRawData;
}
public void setCrcRawData(byte[] crcRawData) {
this.crcRawData = crcRawData;
}
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 boolean isEncrypted() {
return isEncrypted;
}
public void setEncrypted(boolean encrypted) {
isEncrypted = encrypted;
}
public EncryptionMethod getEncryptionMethod() {
return encryptionMethod;
}
public void setEncryptionMethod(EncryptionMethod encryptionMethod) {
this.encryptionMethod = encryptionMethod;
}
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;
}
public List<ExtraDataRecord> getExtraDataRecords() {
return extraDataRecords;
}
public void setExtraDataRecords(List<ExtraDataRecord> extraDataRecords) {
this.extraDataRecords = extraDataRecords;
}
public boolean isDirectory() {
return isDirectory;
}
public void setDirectory(boolean directory) {
isDirectory = directory;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof AbstractFileHeader)) {
return false;
}
return this.getFileName().equals(((AbstractFileHeader) obj).getFileName());
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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 net.lingala.zip4j.model;
public class ArchiveExtraDataRecord extends ZipHeader {
private int extraFieldLength;
private String extraFieldData;
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;
}
}

View File

@@ -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 net.lingala.zip4j.model;
import java.util.ArrayList;
import java.util.List;
public class CentralDirectory {
private List<FileHeader> fileHeaders = new ArrayList<>();
private DigitalSignature digitalSignature = new DigitalSignature();
public List<FileHeader> getFileHeaders() {
return fileHeaders;
}
public void setFileHeaders(List<FileHeader> fileHeaders) {
this.fileHeaders = fileHeaders;
}
public DigitalSignature getDigitalSignature() {
return digitalSignature;
}
public void setDigitalSignature(DigitalSignature digitalSignature) {
this.digitalSignature = digitalSignature;
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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 net.lingala.zip4j.model;
public class DataDescriptor extends ZipHeader {
private long crc;
private long compressedSize;
private long uncompressedSize;
public long getCrc() {
return crc;
}
public void setCrc(long crc) {
this.crc = crc;
}
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;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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 net.lingala.zip4j.model;
public class DigitalSignature extends ZipHeader {
private int sizeOfData;
private String signatureData;
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;
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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 net.lingala.zip4j.model;
import net.lingala.zip4j.headers.HeaderSignature;
public class EndOfCentralDirectoryRecord extends ZipHeader {
private int numberOfThisDisk;
private int numberOfThisDiskStartOfCentralDir;
private int totalNumberOfEntriesInCentralDirectoryOnThisDisk;
private int totalNumberOfEntriesInCentralDirectory;
private int sizeOfCentralDirectory;
private long offsetOfStartOfCentralDirectory;
private long offsetOfEndOfCentralDirectory;
private String comment = "";
public EndOfCentralDirectoryRecord() {
setSignature(HeaderSignature.END_OF_CENTRAL_DIRECTORY);
}
public int getNumberOfThisDisk() {
return numberOfThisDisk;
}
public void setNumberOfThisDisk(int numberOfThisDisk) {
this.numberOfThisDisk = numberOfThisDisk;
}
public int getNumberOfThisDiskStartOfCentralDir() {
return numberOfThisDiskStartOfCentralDir;
}
public void setNumberOfThisDiskStartOfCentralDir(int numberOfThisDiskStartOfCentralDir) {
this.numberOfThisDiskStartOfCentralDir = numberOfThisDiskStartOfCentralDir;
}
public int getTotalNumberOfEntriesInCentralDirectoryOnThisDisk() {
return totalNumberOfEntriesInCentralDirectoryOnThisDisk;
}
public void setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(
int totalNumberOfEntriesInCentralDirectoryOnThisDisk) {
this.totalNumberOfEntriesInCentralDirectoryOnThisDisk = totalNumberOfEntriesInCentralDirectoryOnThisDisk;
}
public int getTotalNumberOfEntriesInCentralDirectory() {
return totalNumberOfEntriesInCentralDirectory;
}
public void setTotalNumberOfEntriesInCentralDirectory(int totNoOfEntrisInCentralDir) {
this.totalNumberOfEntriesInCentralDirectory = totNoOfEntrisInCentralDir;
}
public int getSizeOfCentralDirectory() {
return sizeOfCentralDirectory;
}
public void setSizeOfCentralDirectory(int sizeOfCentralDirectory) {
this.sizeOfCentralDirectory = sizeOfCentralDirectory;
}
public long getOffsetOfStartOfCentralDirectory() {
return offsetOfStartOfCentralDirectory;
}
public void setOffsetOfStartOfCentralDirectory(long offSetOfStartOfCentralDir) {
this.offsetOfStartOfCentralDirectory = offSetOfStartOfCentralDir;
}
public long getOffsetOfEndOfCentralDirectory() {
return offsetOfEndOfCentralDirectory;
}
public void setOffsetOfEndOfCentralDirectory(long offsetOfEndOfCentralDirectory) {
this.offsetOfEndOfCentralDirectory = offsetOfEndOfCentralDirectory;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
if (comment != null) {
this.comment = comment;
}
}
}

View File

@@ -0,0 +1,9 @@
package net.lingala.zip4j.model;
import java.io.File;
public interface ExcludeFileFilter {
boolean isExcluded(File file);
}

View File

@@ -0,0 +1,49 @@
/*
* 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 net.lingala.zip4j.model;
public class ExtraDataRecord extends ZipHeader {
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;
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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 net.lingala.zip4j.model;
import net.lingala.zip4j.headers.HeaderSignature;
public class FileHeader extends AbstractFileHeader {
private int versionMadeBy;
private int fileCommentLength = 0;
private int diskNumberStart;
private byte[] internalFileAttributes;
private byte[] externalFileAttributes;
private long offsetLocalHeader;
private String fileComment;
public FileHeader() {
setSignature(HeaderSignature.CENTRAL_DIRECTORY);
}
public int getVersionMadeBy() {
return versionMadeBy;
}
public void setVersionMadeBy(int versionMadeBy) {
this.versionMadeBy = versionMadeBy;
}
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[] getInternalFileAttributes() {
return internalFileAttributes;
}
public void setInternalFileAttributes(byte[] internalFileAttributes) {
this.internalFileAttributes = internalFileAttributes;
}
public byte[] getExternalFileAttributes() {
return externalFileAttributes;
}
public void setExternalFileAttributes(byte[] externalFileAttributes) {
this.externalFileAttributes = externalFileAttributes;
}
public long getOffsetLocalHeader() {
return offsetLocalHeader;
}
public void setOffsetLocalHeader(long offsetLocalHeader) {
this.offsetLocalHeader = offsetLocalHeader;
}
public String getFileComment() {
return fileComment;
}
public void setFileComment(String fileComment) {
this.fileComment = fileComment;
}
@Override
public String toString() {
return getFileName();
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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 net.lingala.zip4j.model;
import net.lingala.zip4j.headers.HeaderSignature;
public class LocalFileHeader extends AbstractFileHeader {
private byte[] extraField;
private long offsetStartOfData;
private boolean writeCompressedSizeInZip64ExtraRecord;
public LocalFileHeader() {
setSignature(HeaderSignature.LOCAL_FILE_HEADER);
}
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 isWriteCompressedSizeInZip64ExtraRecord() {
return writeCompressedSizeInZip64ExtraRecord;
}
public void setWriteCompressedSizeInZip64ExtraRecord(boolean writeCompressedSizeInZip64ExtraRecord) {
this.writeCompressedSizeInZip64ExtraRecord = writeCompressedSizeInZip64ExtraRecord;
}
}

View File

@@ -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 net.lingala.zip4j.model;
public class Zip64EndOfCentralDirectoryLocator extends ZipHeader {
private int numberOfDiskStartOfZip64EndOfCentralDirectoryRecord;
private long offsetZip64EndOfCentralDirectoryRecord;
private int totalNumberOfDiscs;
public int getNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord() {
return numberOfDiskStartOfZip64EndOfCentralDirectoryRecord;
}
public void setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(
int noOfDiskStartOfZip64EndOfCentralDirRec) {
this.numberOfDiskStartOfZip64EndOfCentralDirectoryRecord = noOfDiskStartOfZip64EndOfCentralDirRec;
}
public long getOffsetZip64EndOfCentralDirectoryRecord() {
return offsetZip64EndOfCentralDirectoryRecord;
}
public void setOffsetZip64EndOfCentralDirectoryRecord(long offsetZip64EndOfCentralDirectoryRecord) {
this.offsetZip64EndOfCentralDirectoryRecord = offsetZip64EndOfCentralDirectoryRecord;
}
public int getTotalNumberOfDiscs() {
return totalNumberOfDiscs;
}
public void setTotalNumberOfDiscs(int totNumberOfDiscs) {
this.totalNumberOfDiscs = totNumberOfDiscs;
}
}

View File

@@ -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 net.lingala.zip4j.model;
public class Zip64EndOfCentralDirectoryRecord extends ZipHeader {
private long sizeOfZip64EndCentralDirectoryRecord;
private int versionMadeBy;
private int versionNeededToExtract;
private int numberOfThisDisk;
private int numberOfThisDiskStartOfCentralDirectory;
private long totalNumberOfEntriesInCentralDirectoryOnThisDisk;
private long totalNumberOfEntriesInCentralDirectory;
private long sizeOfCentralDirectory;
private long offsetStartCentralDirectoryWRTStartDiskNumber = -1;
private byte[] extensibleDataSector;
public long getSizeOfZip64EndCentralDirectoryRecord() {
return sizeOfZip64EndCentralDirectoryRecord;
}
public void setSizeOfZip64EndCentralDirectoryRecord(long sizeOfZip64EndCentralDirectoryRecord) {
this.sizeOfZip64EndCentralDirectoryRecord = sizeOfZip64EndCentralDirectoryRecord;
}
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 getNumberOfThisDisk() {
return numberOfThisDisk;
}
public void setNumberOfThisDisk(int numberOfThisDisk) {
this.numberOfThisDisk = numberOfThisDisk;
}
public int getNumberOfThisDiskStartOfCentralDirectory() {
return numberOfThisDiskStartOfCentralDirectory;
}
public void setNumberOfThisDiskStartOfCentralDirectory(int numberOfThisDiskStartOfCentralDirectory) {
this.numberOfThisDiskStartOfCentralDirectory = numberOfThisDiskStartOfCentralDirectory;
}
public long getTotalNumberOfEntriesInCentralDirectoryOnThisDisk() {
return totalNumberOfEntriesInCentralDirectoryOnThisDisk;
}
public void setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(
long totalNumberOfEntriesInCentralDirectoryOnThisDisk) {
this.totalNumberOfEntriesInCentralDirectoryOnThisDisk = totalNumberOfEntriesInCentralDirectoryOnThisDisk;
}
public long getTotalNumberOfEntriesInCentralDirectory() {
return totalNumberOfEntriesInCentralDirectory;
}
public void setTotalNumberOfEntriesInCentralDirectory(long totalNumberOfEntriesInCentralDirectory) {
this.totalNumberOfEntriesInCentralDirectory = totalNumberOfEntriesInCentralDirectory;
}
public long getSizeOfCentralDirectory() {
return sizeOfCentralDirectory;
}
public void setSizeOfCentralDirectory(long sizeOfCentralDirectory) {
this.sizeOfCentralDirectory = sizeOfCentralDirectory;
}
public long getOffsetStartCentralDirectoryWRTStartDiskNumber() {
return offsetStartCentralDirectoryWRTStartDiskNumber;
}
public void setOffsetStartCentralDirectoryWRTStartDiskNumber(
long offsetStartCentralDirectoryWRTStartDiskNumber) {
this.offsetStartCentralDirectoryWRTStartDiskNumber = offsetStartCentralDirectoryWRTStartDiskNumber;
}
public byte[] getExtensibleDataSector() {
return extensibleDataSector;
}
public void setExtensibleDataSector(byte[] extensibleDataSector) {
this.extensibleDataSector = extensibleDataSector;
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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 net.lingala.zip4j.model;
public class Zip64ExtendedInfo extends ZipHeader {
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 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;
}
}

View File

@@ -0,0 +1,16 @@
package net.lingala.zip4j.model;
import net.lingala.zip4j.headers.HeaderSignature;
public abstract class ZipHeader {
private HeaderSignature signature;
public HeaderSignature getSignature() {
return signature;
}
public void setSignature(HeaderSignature signature) {
this.signature = signature;
}
}

View File

@@ -0,0 +1,163 @@
/*
* 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 net.lingala.zip4j.model;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ZipModel implements Cloneable {
private List<LocalFileHeader> localFileHeaders = new ArrayList<>();
private List<DataDescriptor> dataDescriptors = new ArrayList<>();
private ArchiveExtraDataRecord archiveExtraDataRecord = new ArchiveExtraDataRecord();
private CentralDirectory centralDirectory = new CentralDirectory();
private EndOfCentralDirectoryRecord endOfCentralDirectoryRecord = new EndOfCentralDirectoryRecord();
private Zip64EndOfCentralDirectoryLocator zip64EndOfCentralDirectoryLocator = new Zip64EndOfCentralDirectoryLocator();
private Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectoryRecord = new Zip64EndOfCentralDirectoryRecord();
private boolean splitArchive;
private long splitLength;
private File zipFile;
private boolean isZip64Format = false;
private boolean isNestedZipFile;
private long start;
private long end;
public ZipModel() {
splitLength = -1;
}
public List<LocalFileHeader> getLocalFileHeaders() {
return localFileHeaders;
}
public void setLocalFileHeaders(List<LocalFileHeader> localFileHeaderList) {
this.localFileHeaders = localFileHeaderList;
}
public List<DataDescriptor> getDataDescriptors() {
return dataDescriptors;
}
public void setDataDescriptors(List<DataDescriptor> dataDescriptors) {
this.dataDescriptors = dataDescriptors;
}
public CentralDirectory getCentralDirectory() {
return centralDirectory;
}
public void setCentralDirectory(CentralDirectory centralDirectory) {
this.centralDirectory = centralDirectory;
}
public EndOfCentralDirectoryRecord getEndOfCentralDirectoryRecord() {
return endOfCentralDirectoryRecord;
}
public void setEndOfCentralDirectoryRecord(EndOfCentralDirectoryRecord endOfCentralDirectoryRecord) {
this.endOfCentralDirectoryRecord = endOfCentralDirectoryRecord;
}
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 File getZipFile() {
return zipFile;
}
public void setZipFile(File zipFile) {
this.zipFile = zipFile;
}
public Zip64EndOfCentralDirectoryLocator getZip64EndOfCentralDirectoryLocator() {
return zip64EndOfCentralDirectoryLocator;
}
public void setZip64EndOfCentralDirectoryLocator(
Zip64EndOfCentralDirectoryLocator zip64EndOfCentralDirectoryLocator) {
this.zip64EndOfCentralDirectoryLocator = zip64EndOfCentralDirectoryLocator;
}
public Zip64EndOfCentralDirectoryRecord getZip64EndOfCentralDirectoryRecord() {
return zip64EndOfCentralDirectoryRecord;
}
public void setZip64EndOfCentralDirectoryRecord(
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectoryRecord) {
this.zip64EndOfCentralDirectoryRecord = zip64EndOfCentralDirectoryRecord;
}
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();
}
}

View File

@@ -0,0 +1,419 @@
/*
* 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 net.lingala.zip4j.model;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import net.lingala.zip4j.model.enums.AesVersion;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
/**
* Encapsulates the parameters that that control how Zip4J encodes data
*/
public class ZipParameters {
/**
* Indicates the action to take when a symbolic link is added to the ZIP file
*/
public enum SymbolicLinkAction {
/**
* Add only the symbolic link itself, not the target file or its contents
*/
INCLUDE_LINK_ONLY,
/**
* Add only the target file and its contents, using the filename of the symbolic link
*/
INCLUDE_LINKED_FILE_ONLY,
/**
* Add the symbolic link itself and the target file with its original filename and its contents
*/
INCLUDE_LINK_AND_LINKED_FILE
};
private CompressionMethod compressionMethod = CompressionMethod.DEFLATE;
private CompressionLevel compressionLevel = CompressionLevel.NORMAL;
private boolean encryptFiles = false;
private EncryptionMethod encryptionMethod = EncryptionMethod.NONE;
private boolean readHiddenFiles = true;
private boolean readHiddenFolders = true;
private AesKeyStrength aesKeyStrength = AesKeyStrength.KEY_STRENGTH_256;
private AesVersion aesVersion = AesVersion.TWO;
private boolean includeRootFolder = true;
private long entryCRC;
private String defaultFolderPath;
private String fileNameInZip;
private long lastModifiedFileTime = System.currentTimeMillis();
private long entrySize = -1;
private boolean writeExtendedLocalFileHeader = true;
private boolean overrideExistingFilesInZip = true;
private String rootFolderNameInZip;
private String fileComment;
private SymbolicLinkAction symbolicLinkAction = SymbolicLinkAction.INCLUDE_LINKED_FILE_ONLY;
private ExcludeFileFilter excludeFileFilter;
private boolean unixMode;
/**
* Create a ZipParameters instance with default values;
* CompressionMethod.DEFLATE, CompressionLevel.NORMAL, EncryptionMethod.NONE,
* AesKeyStrength.KEY_STRENGTH_256, AesVerson.Two, SymbolicLinkAction.INCLUDE_LINKED_FILE_ONLY,
* readHiddenFiles is true, readHiddenFolders is true, includeRootInFolder is true,
* writeExtendedLocalFileHeader is true, overrideExistingFilesInZip is true
*/
public ZipParameters() {
}
/**
* Create a clone of given ZipParameters instance
* @param zipParameters the ZipParameters instance to clone
*/
public ZipParameters(ZipParameters zipParameters) {
this.compressionMethod = zipParameters.getCompressionMethod();
this.compressionLevel = zipParameters.getCompressionLevel();
this.encryptFiles = zipParameters.isEncryptFiles();
this.encryptionMethod = zipParameters.getEncryptionMethod();
this.readHiddenFiles = zipParameters.isReadHiddenFiles();
this.readHiddenFolders = zipParameters.isReadHiddenFolders();
this.aesKeyStrength = zipParameters.getAesKeyStrength();
this.aesVersion = zipParameters.getAesVersion();
this.includeRootFolder = zipParameters.isIncludeRootFolder();
this.entryCRC = zipParameters.getEntryCRC();
this.defaultFolderPath = zipParameters.getDefaultFolderPath();
this.fileNameInZip = zipParameters.getFileNameInZip();
this.lastModifiedFileTime = zipParameters.getLastModifiedFileTime();
this.entrySize = zipParameters.getEntrySize();
this.writeExtendedLocalFileHeader = zipParameters.isWriteExtendedLocalFileHeader();
this.overrideExistingFilesInZip = zipParameters.isOverrideExistingFilesInZip();
this.rootFolderNameInZip = zipParameters.getRootFolderNameInZip();
this.fileComment = zipParameters.getFileComment();
this.symbolicLinkAction = zipParameters.getSymbolicLinkAction();
this.excludeFileFilter = zipParameters.getExcludeFileFilter();
this.unixMode = zipParameters.isUnixMode();
}
/**
* Get the compression method specified in this ZipParameters
* @return the ZIP compression method
*/
public CompressionMethod getCompressionMethod() {
return compressionMethod;
}
/**
* Set the ZIP compression method
* @param compressionMethod the ZIP compression method
*/
public void setCompressionMethod(CompressionMethod compressionMethod) {
this.compressionMethod = compressionMethod;
}
/**
* Test if files files are to be encrypted
* @return true if files are to be encrypted
*/
public boolean isEncryptFiles() {
return encryptFiles;
}
/**
* Set the flag indicating that files are to be encrypted
* @param encryptFiles if true, files will be encrypted
*/
public void setEncryptFiles(boolean encryptFiles) {
this.encryptFiles = encryptFiles;
}
/**
* Get the encryption method used to encrypt files
* @return the encryption method
*/
public EncryptionMethod getEncryptionMethod() {
return encryptionMethod;
}
/**
* Set the encryption method used to encrypt files
* @param encryptionMethod the encryption method to be used
*/
public void setEncryptionMethod(EncryptionMethod encryptionMethod) {
this.encryptionMethod = encryptionMethod;
}
/**
* Get the compression level used to compress files
* @return the compression level used to compress files
*/
public CompressionLevel getCompressionLevel() {
return compressionLevel;
}
/**
* Set the compression level used to compress files
* @param compressionLevel the compression level used to compress files
*/
public void setCompressionLevel(CompressionLevel compressionLevel) {
this.compressionLevel = compressionLevel;
}
/**
* Test if hidden files will be included during folder recursion
*
* @return true if hidden files will be included when adding folders to the zip
*/
public boolean isReadHiddenFiles() {
return readHiddenFiles;
}
/**
* Indicate if hidden files will be included during folder recursion
*
* @param readHiddenFiles if true, hidden files will be included when adding folders to the zip
*/
public void setReadHiddenFiles(boolean readHiddenFiles) {
this.readHiddenFiles = readHiddenFiles;
}
/**
* Test if hidden folders will be included during folder recursion
*
* @return true if hidden folders will be included when adding folders to the zip
*/
public boolean isReadHiddenFolders() {
return readHiddenFolders;
}
/**
* Indicate if hidden folders will be included during folder recursion
* @param readHiddenFolders if true, hidden folders will be included when added folders to the zip
*/
public void setReadHiddenFolders(boolean readHiddenFolders) {
this.readHiddenFolders = readHiddenFolders;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* Get the key strength of the AES encryption key
* @return the key strength of the AES encryption key
*/
public AesKeyStrength getAesKeyStrength() {
return aesKeyStrength;
}
/**
* Set the key strength of the AES encryption key
* @param aesKeyStrength the key strength of the AES encryption key
*/
public void setAesKeyStrength(AesKeyStrength aesKeyStrength) {
this.aesKeyStrength = aesKeyStrength;
}
/**
* Get the AES format version used for encryption
* @return the AES format version used for encryption
*/
public AesVersion getAesVersion() {
return aesVersion;
}
/**
* Set the AES format version to use for encryption
* @param aesVersion the AES format version to use
*/
public void setAesVersion(AesVersion aesVersion) {
this.aesVersion = aesVersion;
}
/**
* Test if the parent folder of the added files will be included in the ZIP
* @return true if the parent folder of the added files will be included into the zip
*/
public boolean isIncludeRootFolder() {
return includeRootFolder;
}
/**
* Set the flag to indicate if the parent folder of added files will be included in the ZIP
* @param includeRootFolder if true, the parent folder of added files will be included in the ZIP
*/
public void setIncludeRootFolder(boolean includeRootFolder) {
this.includeRootFolder = includeRootFolder;
}
public long getEntryCRC() {
return entryCRC;
}
public void setEntryCRC(long entryCRC) {
this.entryCRC = entryCRC;
}
public String getDefaultFolderPath() {
return defaultFolderPath;
}
public void setDefaultFolderPath(String defaultFolderPath) {
this.defaultFolderPath = defaultFolderPath;
}
public String getFileNameInZip() {
return fileNameInZip;
}
/**
* Set the filename that will be used to include a file into the ZIP file to a different name
* that given by the source filename added to the ZIP file. The filenameInZip must
* adhere to the ZIP filename specification, including the use of forward slash '/' as the
* directory separator, and it must also be a relative file. If the filenameInZip given is not null and
* not empty, the value specified by setRootFolderNameInZip() will be ignored.
*
* @param fileNameInZip the filename to set in the ZIP. Use null or an empty String to set the default behavior
*/
public void setFileNameInZip(String fileNameInZip) {
this.fileNameInZip = fileNameInZip;
}
/**
* Get the last modified time to be used for files written to the ZIP
* @return the last modified time in milliseconds since the epoch
*/
public long getLastModifiedFileTime() {
return lastModifiedFileTime;
}
/**
* Set the last modified time recorded in the ZIP file for the added files. If less than 0,
* the last modified time is cleared and the current time is used
* @param lastModifiedFileTime the last modified time in milliseconds since the epoch
*/
public void setLastModifiedFileTime(long lastModifiedFileTime) {
if (lastModifiedFileTime <= 0) {
return;
}
this.lastModifiedFileTime = lastModifiedFileTime;
}
public long getEntrySize() {
return entrySize;
}
public void setEntrySize(long entrySize) {
this.entrySize = entrySize;
}
public boolean isWriteExtendedLocalFileHeader() {
return writeExtendedLocalFileHeader;
}
public void setWriteExtendedLocalFileHeader(boolean writeExtendedLocalFileHeader) {
this.writeExtendedLocalFileHeader = writeExtendedLocalFileHeader;
}
public boolean isOverrideExistingFilesInZip() {
return overrideExistingFilesInZip;
}
/**
* Set the behavior if a file is added that already exists in the ZIP.
* @param overrideExistingFilesInZip if true, remove the existing file in the ZIP; if false do not add the new file
*/
public void setOverrideExistingFilesInZip(boolean overrideExistingFilesInZip) {
this.overrideExistingFilesInZip = overrideExistingFilesInZip;
}
public String getRootFolderNameInZip() {
return rootFolderNameInZip;
}
/**
* Set the folder name that will be prepended to the filename in the ZIP. This value is ignored
* if setFileNameInZip() is specified with a non-null, non-empty string.
*
* @param rootFolderNameInZip the name of the folder to be prepended to the filename
* in the ZIP archive
*/
public void setRootFolderNameInZip(String rootFolderNameInZip) {
this.rootFolderNameInZip = rootFolderNameInZip;
}
/**
* Get the file comment
* @return the file comment
*/
public String getFileComment() {
return fileComment;
}
/**
* Set the file comment
* @param fileComment the file comment
*/
public void setFileComment(String fileComment) {
this.fileComment = fileComment;
}
/**
* Get the behavior when adding a symbolic link
* @return the behavior when adding a symbolic link
*/
public SymbolicLinkAction getSymbolicLinkAction() {
return symbolicLinkAction;
}
/**
* Set the behavior when adding a symbolic link
* @param symbolicLinkAction the behavior when adding a symbolic link
*/
public void setSymbolicLinkAction(SymbolicLinkAction symbolicLinkAction) {
this.symbolicLinkAction = symbolicLinkAction;
}
/**
* Returns the file exclusion filter that is currently being used when adding files/folders to zip file
* @return ExcludeFileFilter
*/
public ExcludeFileFilter getExcludeFileFilter() {
return excludeFileFilter;
}
/**
* Set a filter to exclude any files from the list of files being added to zip. Mostly used when adding a folder
* to a zip, and if certain files have to be excluded from adding to the zip file.
*/
public void setExcludeFileFilter(ExcludeFileFilter excludeFileFilter) {
this.excludeFileFilter = excludeFileFilter;
}
/**
* Returns true if zip4j is using unix mode as default. Returns False otherwise.
* @return true if zip4j is using unix mode as default, false otherwise
*/
public boolean isUnixMode() {
return unixMode;
}
/**
* When set to true, zip4j uses unix mode as default when generating file headers.
* @param unixMode
*/
public void setUnixMode(boolean unixMode) {
this.unixMode = unixMode;
}
}

View File

@@ -0,0 +1,71 @@
package net.lingala.zip4j.model.enums;
/**
* Indicates the AES encryption key length
*
*/
public enum AesKeyStrength {
/**
* 128-bit AES key length
*/
KEY_STRENGTH_128(1, 8, 16, 16),
/**
* 192-bit AES key length
*/
KEY_STRENGTH_192(2, 12, 24, 24),
/**
* 256-bit AES key length
*/
KEY_STRENGTH_256(3, 16, 32, 32);
private int rawCode;
private int saltLength;
private int macLength;
private int keyLength;
AesKeyStrength(int rawCode, int saltLength, int macLength, int keyLength) {
this.rawCode = rawCode;
this.saltLength = saltLength;
this.macLength = macLength;
this.keyLength = keyLength;
}
/**
* Get the code written to the ZIP file
* @return the code written the ZIP file
*/
public int getRawCode() {
return rawCode;
}
public int getSaltLength() {
return saltLength;
}
public int getMacLength() {
return macLength;
}
/**
* Get the key length in bytes that this AesKeyStrength represents
* @return the key length in bytes
*/
public int getKeyLength() {
return keyLength;
}
/**
* Get a AesKeyStrength given a code from the ZIP file
* @param code the code from the ZIP file
* @return the AesKeyStrength that represents the given code, or null if the code does not match
*/
public static AesKeyStrength getAesKeyStrengthFromRawCode(int code) {
for (AesKeyStrength aesKeyStrength : values()) {
if (aesKeyStrength.getRawCode() == code) {
return aesKeyStrength;
}
}
return null;
}
}

View File

@@ -0,0 +1,44 @@
package net.lingala.zip4j.model.enums;
/**
* Indicates the AES format used
*/
public enum AesVersion {
/**
* Version 1 of the AES format
*/
ONE(1),
/**
* Version 2 of the AES format
*/
TWO(2);
private int versionNumber;
AesVersion(int versionNumber) {
this.versionNumber = versionNumber;
}
/**
* Get the AES version number as an integer
* @return the AES version number
*/
public int getVersionNumber() {
return versionNumber;
}
/**
* Get the AESVersion instance from an integer AES version number
* @return the AESVersion instance for a given version
* @throws IllegalArgumentException if an unsupported version is given
*/
public static AesVersion getFromVersionNumber(int versionNumber) {
for (AesVersion aesVersion : values()) {
if (aesVersion.versionNumber == versionNumber) {
return aesVersion;
}
}
throw new IllegalArgumentException("Unsupported Aes version");
}
}

View File

@@ -0,0 +1,48 @@
package net.lingala.zip4j.model.enums;
/**
* Indicates the level of compression for the DEFLATE compression method
*
*/
public enum CompressionLevel {
/**
* Level 1 Deflate compression
* @see java.util.zip.Deflater#BEST_SPEED
*/
FASTEST(1),
/**
* Level 3 Deflate compression
* @see java.util.zip.Deflater
*/
FAST(3),
/**
* Level 5 Deflate compression
* @see java.util.zip.Deflater
*/
NORMAL(5),
/**
* Level 7 Deflate compression
* @see java.util.zip.Deflater
*/
MAXIMUM(7),
/**
* Level 9 Deflate compression. Not part of the original ZIP format specification.
* @see java.util.zip.Deflater#BEST_COMPRESSION
*/
ULTRA(9);
private int level;
CompressionLevel(int level) {
this.level = level;
}
/**
* Get the Deflate compression level (0-9) for this CompressionLevel
* @return the deflate compression level
*/
public int getLevel() {
return level;
}
}

View File

@@ -0,0 +1,54 @@
package net.lingala.zip4j.model.enums;
import net.lingala.zip4j.exception.ZipException;
/**
* Indicates the algorithm used for compression
*
*/
public enum CompressionMethod {
/**
* No compression is performed
*/
STORE(0),
/**
* The Deflate compression is used.
* @see java.util.zip.Deflater
*/
DEFLATE(8),
/**
* For internal use in Zip4J
*/
AES_INTERNAL_ONLY(99);
private int code;
CompressionMethod(int code) {
this.code = code;
}
/**
* Get the code used in the ZIP file for this CompressionMethod
* @return the code used in the ZIP file
*/
public int getCode() {
return code;
}
/**
* Get the CompressionMethod for a given ZIP file code
* @param code the code for a compression method
* @return the CompressionMethod related to the given code
* @throws ZipException on unknown code
*/
public static CompressionMethod getCompressionMethodFromCode(int code) throws ZipException {
for (CompressionMethod compressionMethod : values()) {
if (compressionMethod.getCode() == code) {
return compressionMethod;
}
}
throw new ZipException("Unknown compression method", ZipException.Type.UNKNOWN_COMPRESSION_METHOD);
}
}

View File

@@ -0,0 +1,27 @@
package net.lingala.zip4j.model.enums;
/**
* Indicates the encryption method used in the ZIP file
*
*/
public enum EncryptionMethod {
/**
* No encryption is performed
*/
NONE,
/**
* Encrypted with the weak ZIP standard algorithm
*/
ZIP_STANDARD,
/**
* Encrypted with the stronger ZIP standard algorithm
*/
ZIP_STANDARD_VARIANT_STRONG,
/**
* Encrypted with AES, the strongest choice but currently
* cannot be expanded in Windows Explorer
*/
AES
}

View File

@@ -0,0 +1,17 @@
package net.lingala.zip4j.model.enums;
public enum RandomAccessFileMode {
READ("r"),
WRITE("rw");
private String value;
RandomAccessFileMode(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,162 @@
/*
* 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 net.lingala.zip4j.progress;
/**
* If Zip4j is set to run in thread mode, this class helps retrieve current progress
*/
public class ProgressMonitor {
public enum State { READY, BUSY }
public enum Result { SUCCESS, WORK_IN_PROGRESS, ERROR, CANCELLED }
public enum Task { NONE, ADD_ENTRY, REMOVE_ENTRY, CALCULATE_CRC, EXTRACT_ENTRY, MERGE_ZIP_FILES, SET_COMMENT, RENAME_FILE}
private State state;
private long totalWork;
private long workCompleted;
private int percentDone;
private Task currentTask;
private String fileName;
private Result result;
private Exception exception;
private boolean cancelAllTasks;
private boolean pause;
public ProgressMonitor() {
reset();
}
public void updateWorkCompleted(long workCompleted) {
this.workCompleted += workCompleted;
if (totalWork > 0) {
percentDone = (int) ((this.workCompleted * 100 / totalWork));
if (percentDone > 100) {
percentDone = 100;
}
}
while (pause) {
try {
Thread.sleep(150);
} catch (InterruptedException e) {
//Do nothing
}
}
}
public void endProgressMonitor() {
result = Result.SUCCESS;
percentDone = 100;
reset();
}
public void endProgressMonitor(Exception e) {
result = Result.ERROR;
exception = e;
reset();
}
public void fullReset() {
reset();
fileName = null;
totalWork = 0;
workCompleted = 0;
percentDone = 0;
}
private void reset() {
currentTask = Task.NONE;
state = State.READY;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public long getTotalWork() {
return totalWork;
}
public void setTotalWork(long totalWork) {
this.totalWork = totalWork;
}
public long getWorkCompleted() {
return workCompleted;
}
public int getPercentDone() {
return percentDone;
}
public void setPercentDone(int percentDone) {
this.percentDone = percentDone;
}
public Task getCurrentTask() {
return currentTask;
}
public void setCurrentTask(Task currentTask) {
this.currentTask = currentTask;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Result getResult() {
return result;
}
public void setResult(Result result) {
this.result = result;
}
public Exception getException() {
return exception;
}
public void setException(Exception exception) {
this.exception = exception;
}
public boolean isCancelAllTasks() {
return cancelAllTasks;
}
public void setCancelAllTasks(boolean cancelAllTasks) {
this.cancelAllTasks = cancelAllTasks;
}
public boolean isPause() {
return pause;
}
public void setPause(boolean pause) {
this.pause = pause;
}
}

View File

@@ -0,0 +1,291 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderUtil;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.io.outputstream.SplitOutputStream;
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.RemoveFilesFromZipTask.RemoveFilesFromZipTaskParameters;
import net.lingala.zip4j.util.BitUtils;
import net.lingala.zip4j.util.FileUtils;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.Zip4jUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static net.lingala.zip4j.headers.HeaderUtil.getFileHeader;
import static net.lingala.zip4j.model.ZipParameters.SymbolicLinkAction.INCLUDE_LINK_AND_LINKED_FILE;
import static net.lingala.zip4j.model.ZipParameters.SymbolicLinkAction.INCLUDE_LINK_ONLY;
import static net.lingala.zip4j.model.enums.CompressionMethod.DEFLATE;
import static net.lingala.zip4j.model.enums.CompressionMethod.STORE;
import static net.lingala.zip4j.model.enums.EncryptionMethod.NONE;
import static net.lingala.zip4j.model.enums.EncryptionMethod.ZIP_STANDARD;
import static net.lingala.zip4j.progress.ProgressMonitor.Task.*;
import static net.lingala.zip4j.util.CrcUtil.computeFileCrc;
import static net.lingala.zip4j.util.FileUtils.assertFilesExist;
import static net.lingala.zip4j.util.FileUtils.getRelativeFileName;
import static net.lingala.zip4j.util.InternalZipConstants.BUFF_SIZE;
import static net.lingala.zip4j.util.Zip4jUtil.epochToExtendedDosTime;
public abstract class AbstractAddFileToZipTask<T> extends AsyncZipTask<T> {
private ZipModel zipModel;
private char[] password;
private HeaderWriter headerWriter;
private byte[] readBuff = new byte[BUFF_SIZE];
private int readLen = -1;
AbstractAddFileToZipTask(ZipModel zipModel, char[] password, HeaderWriter headerWriter,
AsyncTaskParameters asyncTaskParameters) {
super(asyncTaskParameters);
this.zipModel = zipModel;
this.password = password;
this.headerWriter = headerWriter;
}
void addFilesToZip(List<File> filesToAdd, ProgressMonitor progressMonitor, ZipParameters zipParameters, Charset charset)
throws IOException {
assertFilesExist(filesToAdd, zipParameters.getSymbolicLinkAction());
List<File> updatedFilesToAdd = removeFilesIfExists(filesToAdd, zipParameters, progressMonitor, charset);
try (SplitOutputStream splitOutputStream = new SplitOutputStream(zipModel.getZipFile(), zipModel.getSplitLength());
ZipOutputStream zipOutputStream = initializeOutputStream(splitOutputStream, charset)) {
for (File fileToAdd : updatedFilesToAdd) {
verifyIfTaskIsCancelled();
ZipParameters clonedZipParameters = cloneAndAdjustZipParameters(zipParameters, fileToAdd, progressMonitor);
progressMonitor.setFileName(fileToAdd.getAbsolutePath());
if (FileUtils.isSymbolicLink(fileToAdd)) {
if (addSymlink(clonedZipParameters)) {
addSymlinkToZip(fileToAdd, zipOutputStream, clonedZipParameters, splitOutputStream);
if (INCLUDE_LINK_ONLY.equals(clonedZipParameters.getSymbolicLinkAction())) {
continue;
}
}
}
addFileToZip(fileToAdd, zipOutputStream, clonedZipParameters, splitOutputStream, progressMonitor);
}
}
}
private void addSymlinkToZip(File fileToAdd, ZipOutputStream zipOutputStream, ZipParameters zipParameters,
SplitOutputStream splitOutputStream) throws IOException {
ZipParameters clonedZipParameters = new ZipParameters(zipParameters);
clonedZipParameters.setFileNameInZip(replaceFileNameInZip(zipParameters.getFileNameInZip(), fileToAdd.getName()));
clonedZipParameters.setEncryptFiles(false);
clonedZipParameters.setCompressionMethod(CompressionMethod.STORE);
zipOutputStream.putNextEntry(clonedZipParameters);
String symLinkTarget = FileUtils.readSymbolicLink(fileToAdd);
zipOutputStream.write(symLinkTarget.getBytes());
closeEntry(zipOutputStream, splitOutputStream, fileToAdd, true);
}
private void addFileToZip(File fileToAdd, ZipOutputStream zipOutputStream, ZipParameters zipParameters,
SplitOutputStream splitOutputStream, ProgressMonitor progressMonitor) throws IOException {
zipOutputStream.putNextEntry(zipParameters);
if (fileToAdd.exists() && !fileToAdd.isDirectory()) {
try (InputStream inputStream = new FileInputStream(fileToAdd)) {
while ((readLen = inputStream.read(readBuff)) != -1) {
zipOutputStream.write(readBuff, 0, readLen);
progressMonitor.updateWorkCompleted(readLen);
verifyIfTaskIsCancelled();
}
}
}
closeEntry(zipOutputStream, splitOutputStream, fileToAdd, false);
}
private void closeEntry(ZipOutputStream zipOutputStream, SplitOutputStream splitOutputStream, File fileToAdd,
boolean isSymlink) throws IOException {
FileHeader fileHeader = zipOutputStream.closeEntry();
byte[] fileAttributes = FileUtils.getFileAttributes(fileToAdd);
if (!isSymlink) {
// Unset the symlink byte if the entry being added is a symlink, but the original file is being added
fileAttributes[3] = BitUtils.unsetBit(fileAttributes[3], 5);
}
fileHeader.setExternalFileAttributes(fileAttributes);
updateLocalFileHeader(fileHeader, splitOutputStream);
}
long calculateWorkForFiles(List<File> filesToAdd, ZipParameters zipParameters) throws ZipException {
long totalWork = 0;
for (File fileToAdd : filesToAdd) {
if (!fileToAdd.exists()) {
continue;
}
if (zipParameters.isEncryptFiles() && zipParameters.getEncryptionMethod() == EncryptionMethod.ZIP_STANDARD) {
totalWork += (fileToAdd.length() * 2); // for CRC calculation
} else {
totalWork += fileToAdd.length();
}
//If an entry already exists, we have to remove that entry first and then add content again.
//In this case, add corresponding work
String relativeFileName = getRelativeFileName(fileToAdd, zipParameters);
FileHeader fileHeader = getFileHeader(getZipModel(), relativeFileName);
if (fileHeader != null) {
totalWork += (getZipModel().getZipFile().length() - fileHeader.getCompressedSize());
}
}
return totalWork;
}
ZipOutputStream initializeOutputStream(SplitOutputStream splitOutputStream, Charset charset) throws IOException {
if (zipModel.getZipFile().exists()) {
splitOutputStream.seek(HeaderUtil.getOffsetStartOfCentralDirectory(zipModel));
}
return new ZipOutputStream(splitOutputStream, password, charset, zipModel);
}
void verifyZipParameters(ZipParameters parameters) throws ZipException {
if (parameters == null) {
throw new ZipException("cannot validate zip parameters");
}
if (parameters.getCompressionMethod() != STORE && parameters.getCompressionMethod() != DEFLATE) {
throw new ZipException("unsupported compression type");
}
if (parameters.isEncryptFiles()) {
if (parameters.getEncryptionMethod() == NONE) {
throw new ZipException("Encryption method has to be set, when encrypt files flag is set");
}
if (password == null || password.length <= 0) {
throw new ZipException("input password is empty or null");
}
} else {
parameters.setEncryptionMethod(NONE);
}
}
void updateLocalFileHeader(FileHeader fileHeader, SplitOutputStream splitOutputStream) throws IOException {
headerWriter.updateLocalFileHeader(fileHeader, getZipModel(), splitOutputStream);
}
private ZipParameters cloneAndAdjustZipParameters(ZipParameters zipParameters, File fileToAdd,
ProgressMonitor progressMonitor) throws IOException {
ZipParameters clonedZipParameters = new ZipParameters(zipParameters);
clonedZipParameters.setLastModifiedFileTime(epochToExtendedDosTime((fileToAdd.lastModified())));
if (fileToAdd.isDirectory()) {
clonedZipParameters.setEntrySize(0);
} else {
clonedZipParameters.setEntrySize(fileToAdd.length());
}
clonedZipParameters.setWriteExtendedLocalFileHeader(false);
clonedZipParameters.setLastModifiedFileTime(fileToAdd.lastModified());
if (!Zip4jUtil.isStringNotNullAndNotEmpty(zipParameters.getFileNameInZip())) {
String relativeFileName = getRelativeFileName(fileToAdd, zipParameters);
clonedZipParameters.setFileNameInZip(relativeFileName);
}
if (fileToAdd.isDirectory()) {
clonedZipParameters.setCompressionMethod(CompressionMethod.STORE);
clonedZipParameters.setEncryptionMethod(EncryptionMethod.NONE);
clonedZipParameters.setEncryptFiles(false);
} else {
if (clonedZipParameters.isEncryptFiles() && clonedZipParameters.getEncryptionMethod() == ZIP_STANDARD) {
progressMonitor.setCurrentTask(CALCULATE_CRC);
clonedZipParameters.setEntryCRC(computeFileCrc(fileToAdd, progressMonitor));
progressMonitor.setCurrentTask(ADD_ENTRY);
}
if (fileToAdd.length() == 0) {
clonedZipParameters.setCompressionMethod(CompressionMethod.STORE);
}
}
return clonedZipParameters;
}
private List<File> removeFilesIfExists(List<File> files, ZipParameters zipParameters, ProgressMonitor progressMonitor, Charset charset)
throws ZipException {
List<File> filesToAdd = new ArrayList<>(files);
if (!zipModel.getZipFile().exists()) {
return filesToAdd;
}
for (File file : files) {
String fileName = getRelativeFileName(file, zipParameters);
FileHeader fileHeader = getFileHeader(zipModel, fileName);
if (fileHeader != null) {
if (zipParameters.isOverrideExistingFilesInZip()) {
progressMonitor.setCurrentTask(REMOVE_ENTRY);
removeFile(fileHeader, progressMonitor, charset);
verifyIfTaskIsCancelled();
progressMonitor.setCurrentTask(ADD_ENTRY);
} else {
filesToAdd.remove(file);
}
}
}
return filesToAdd;
}
void removeFile(FileHeader fileHeader, ProgressMonitor progressMonitor, Charset charset) throws ZipException {
AsyncTaskParameters asyncTaskParameters = new AsyncTaskParameters(null, false, progressMonitor);
RemoveFilesFromZipTask removeFilesFromZipTask = new RemoveFilesFromZipTask(zipModel, headerWriter, asyncTaskParameters);
removeFilesFromZipTask.execute(new RemoveFilesFromZipTaskParameters(Collections.singletonList(fileHeader.getFileName()), charset));
}
private String replaceFileNameInZip(String fileInZipWithPath, String newFileName) {
if (fileInZipWithPath.contains(InternalZipConstants.ZIP_FILE_SEPARATOR)) {
return fileInZipWithPath.substring(0, fileInZipWithPath.lastIndexOf(InternalZipConstants.ZIP_FILE_SEPARATOR) + 1) + newFileName;
}
return newFileName;
}
private boolean addSymlink(ZipParameters zipParameters) {
return INCLUDE_LINK_ONLY.equals(zipParameters.getSymbolicLinkAction()) ||
INCLUDE_LINK_AND_LINKED_FILE.equals(zipParameters.getSymbolicLinkAction());
}
@Override
protected ProgressMonitor.Task getTask() {
return ProgressMonitor.Task.ADD_ENTRY;
}
protected ZipModel getZipModel() {
return zipModel;
}
}

View File

@@ -0,0 +1,177 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.util.BitUtils;
import net.lingala.zip4j.util.UnzipUtil;
import net.lingala.zip4j.util.Zip4jUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import static net.lingala.zip4j.util.InternalZipConstants.BUFF_SIZE;
import static net.lingala.zip4j.util.InternalZipConstants.FILE_SEPARATOR;
public abstract class AbstractExtractFileTask<T> extends AsyncZipTask<T> {
private ZipModel zipModel;
private byte[] buff = new byte[BUFF_SIZE];
public AbstractExtractFileTask(ZipModel zipModel, AsyncTaskParameters asyncTaskParameters) {
super(asyncTaskParameters);
this.zipModel = zipModel;
}
protected void extractFile(ZipInputStream zipInputStream, FileHeader fileHeader, String outputPath,
String newFileName, ProgressMonitor progressMonitor) throws IOException {
if (!outputPath.endsWith(FILE_SEPARATOR)) {
outputPath += FILE_SEPARATOR;
}
File outputFile = determineOutputFile(fileHeader, outputPath, newFileName);
progressMonitor.setFileName(outputFile.getAbsolutePath());
// make sure no file is extracted outside of the target directory (a.k.a zip slip)
String outputCanonicalPath = (new File(outputPath).getCanonicalPath()) + File.separator;
if (!outputFile.getCanonicalPath().startsWith(outputCanonicalPath)) {
throw new ZipException("illegal file name that breaks out of the target directory: "
+ fileHeader.getFileName());
}
verifyNextEntry(zipInputStream, fileHeader);
if (fileHeader.isDirectory()) {
if (!outputFile.exists()) {
if (!outputFile.mkdirs()) {
throw new ZipException("Could not create directory: " + outputFile);
}
}
} else if (isSymbolicLink(fileHeader)) {
createSymLink(zipInputStream, fileHeader, outputFile, progressMonitor);
} else {
checkOutputDirectoryStructure(outputFile);
unzipFile(zipInputStream, fileHeader, outputFile, progressMonitor);
}
}
private boolean isSymbolicLink(FileHeader fileHeader) {
byte[] externalFileAttributes = fileHeader.getExternalFileAttributes();
if (externalFileAttributes == null || externalFileAttributes.length < 4) {
return false;
}
return BitUtils.isBitSet(externalFileAttributes[3], 5);
}
private void unzipFile(ZipInputStream inputStream, FileHeader fileHeader, File outputFile,
ProgressMonitor progressMonitor) throws IOException {
int readLength;
try (OutputStream outputStream = new FileOutputStream(outputFile)) {
while ((readLength = inputStream.read(buff)) != -1) {
outputStream.write(buff, 0, readLength);
progressMonitor.updateWorkCompleted(readLength);
verifyIfTaskIsCancelled();
}
} catch (Exception e) {
if (outputFile.exists()) {
outputFile.delete();
}
throw e;
}
UnzipUtil.applyFileAttributes(fileHeader, outputFile);
}
private void createSymLink(ZipInputStream zipInputStream, FileHeader fileHeader, File outputFile,
ProgressMonitor progressMonitor) throws IOException {
String symLinkPath = new String(readCompleteEntry(zipInputStream, fileHeader, progressMonitor));
if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
throw new ZipException("Could not create parent directories");
}
try {
Path linkTarget = Paths.get(symLinkPath);
Files.createSymbolicLink(outputFile.toPath(), linkTarget);
UnzipUtil.applyFileAttributes(fileHeader, outputFile);
} catch (NoSuchMethodError error) {
try (OutputStream outputStream = new FileOutputStream(outputFile)) {
outputStream.write(symLinkPath.getBytes());
}
}
}
private byte[] readCompleteEntry(ZipInputStream zipInputStream, FileHeader fileHeader,
ProgressMonitor progressMonitor) throws IOException {
byte[] b = new byte[(int) fileHeader.getUncompressedSize()];
int readLength = zipInputStream.read(b);
if (readLength != b.length) {
throw new ZipException("Could not read complete entry");
}
progressMonitor.updateWorkCompleted(b.length);
return b;
}
private void verifyNextEntry(ZipInputStream zipInputStream, FileHeader fileHeader) throws IOException {
if (BitUtils.isBitSet(fileHeader.getGeneralPurposeFlag()[0], 6)) {
throw new ZipException("Entry with name " + fileHeader.getFileName() + " is encrypted with Strong Encryption. " +
"Zip4j does not support Strong Encryption, as this is patented.");
}
LocalFileHeader localFileHeader = zipInputStream.getNextEntry(fileHeader);
if (localFileHeader == null) {
throw new ZipException("Could not read corresponding local file header for file header: "
+ fileHeader.getFileName());
}
if (!fileHeader.getFileName().equals(localFileHeader.getFileName())) {
throw new ZipException("File header and local file header mismatch");
}
}
private void checkOutputDirectoryStructure(File outputFile) throws ZipException {
if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
throw new ZipException("Unable to create parent directories: " + outputFile.getParentFile());
}
}
private File determineOutputFile(FileHeader fileHeader, String outputPath, String newFileName) {
String outputFileName;
if (Zip4jUtil.isStringNotNullAndNotEmpty(newFileName)) {
outputFileName = newFileName;
} else {
outputFileName = getFileNameWithSystemFileSeparators(fileHeader.getFileName()); // replace all slashes with file separator
}
return new File(outputPath + FILE_SEPARATOR + outputFileName);
}
private String getFileNameWithSystemFileSeparators(String fileNameToReplace) {
return fileNameToReplace.replaceAll("[/\\\\]", Matcher.quoteReplacement(FILE_SEPARATOR));
}
@Override
protected ProgressMonitor.Task getTask() {
return ProgressMonitor.Task.EXTRACT_ENTRY;
}
public ZipModel getZipModel() {
return zipModel;
}
}

View File

@@ -0,0 +1,120 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderUtil;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
abstract class AbstractModifyFileTask<T> extends AsyncZipTask<T> {
AbstractModifyFileTask(AsyncTaskParameters asyncTaskParameters) {
super(asyncTaskParameters);
}
File getTemporaryFile(String zipPathWithName) {
Random random = new Random();
File tmpFile = new File(zipPathWithName + random.nextInt(10000));
while (tmpFile.exists()) {
tmpFile = new File(zipPathWithName + random.nextInt(10000));
}
return tmpFile;
}
void updateOffsetsForAllSubsequentFileHeaders(List<FileHeader> sortedFileHeaders, ZipModel zipModel,
FileHeader fileHeaderModified, long offsetToAdd) throws ZipException {
int indexOfFileHeader = getIndexOfFileHeader(sortedFileHeaders, fileHeaderModified);
if (indexOfFileHeader == -1) {
throw new ZipException("Could not locate modified file header in zipModel");
}
for (int i = indexOfFileHeader + 1; i < sortedFileHeaders.size(); i++) {
FileHeader fileHeaderToUpdate = sortedFileHeaders.get(i);
fileHeaderToUpdate.setOffsetLocalHeader(fileHeaderToUpdate.getOffsetLocalHeader() + offsetToAdd);
if (zipModel.isZip64Format()
&& fileHeaderToUpdate.getZip64ExtendedInfo() != null
&& fileHeaderToUpdate.getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {
fileHeaderToUpdate.getZip64ExtendedInfo().setOffsetLocalHeader(
fileHeaderToUpdate.getZip64ExtendedInfo().getOffsetLocalHeader() + offsetToAdd
);
}
}
}
void cleanupFile(boolean successFlag, File zipFile, File temporaryZipFile) throws ZipException {
if (successFlag) {
restoreFileName(zipFile, temporaryZipFile);
} else {
if (!temporaryZipFile.delete()) {
throw new ZipException("Could not delete temporary file");
}
}
}
long copyFile(RandomAccessFile randomAccessFile, OutputStream outputStream, long start, long length,
ProgressMonitor progressMonitor) throws IOException {
FileUtils.copyFile(randomAccessFile, outputStream, start, start + length, progressMonitor);
return length;
}
List<FileHeader> cloneAndSortFileHeadersByOffset(List<FileHeader> allFileHeaders) {
List<FileHeader> clonedFileHeaders = new ArrayList<>(allFileHeaders);
//noinspection Java8ListSort
Collections.sort(clonedFileHeaders, (o1, o2) -> {
if (o1.getFileName().equals(o2.getFileName())) {
return 0;
}
return o1.getOffsetLocalHeader() < o2.getOffsetLocalHeader() ? -1 : 1;
});
return clonedFileHeaders;
}
long getOffsetOfNextEntry(List<FileHeader> sortedFileHeaders, FileHeader fileHeader,
ZipModel zipModel) throws ZipException {
int indexOfFileHeader = getIndexOfFileHeader(sortedFileHeaders, fileHeader);
if (indexOfFileHeader == sortedFileHeaders.size() - 1) {
return HeaderUtil.getOffsetStartOfCentralDirectory(zipModel);
} else {
return sortedFileHeaders.get(indexOfFileHeader + 1).getOffsetLocalHeader();
}
}
private void restoreFileName(File zipFile, File temporaryZipFile) throws ZipException {
if (zipFile.delete()) {
if (!temporaryZipFile.renameTo(zipFile)) {
throw new ZipException("cannot rename modified zip file");
}
} else {
throw new ZipException("cannot delete old zip file");
}
}
private int getIndexOfFileHeader(List<FileHeader> allFileHeaders, FileHeader fileHeaderForIndex) throws ZipException {
for (int i = 0; i < allFileHeaders.size(); i++) {
FileHeader fileHeader = allFileHeaders.get(i);
if (fileHeader.equals(fileHeaderForIndex)) {
return i;
}
}
throw new ZipException("Could not find file header in list of central directory file headers");
}
}

View File

@@ -0,0 +1,12 @@
package net.lingala.zip4j.tasks;
import java.nio.charset.Charset;
public abstract class AbstractZipTaskParameters {
protected Charset charset;
protected AbstractZipTaskParameters(Charset charset) {
this.charset = charset;
}
}

View File

@@ -0,0 +1,49 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.AddFilesToZipTask.AddFilesToZipTaskParameters;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
public class AddFilesToZipTask extends AbstractAddFileToZipTask<AddFilesToZipTaskParameters> {
public AddFilesToZipTask(ZipModel zipModel, char[] password, HeaderWriter headerWriter, AsyncTaskParameters asyncTaskParameters) {
super(zipModel, password, headerWriter, asyncTaskParameters);
}
@Override
protected void executeTask(AddFilesToZipTaskParameters taskParameters, ProgressMonitor progressMonitor)
throws IOException {
verifyZipParameters(taskParameters.zipParameters);
addFilesToZip(taskParameters.filesToAdd, progressMonitor, taskParameters.zipParameters, taskParameters.charset);
}
@Override
protected long calculateTotalWork(AddFilesToZipTaskParameters taskParameters) throws ZipException {
return calculateWorkForFiles(taskParameters.filesToAdd, taskParameters.zipParameters);
}
@Override
protected ProgressMonitor.Task getTask() {
return super.getTask();
}
public static class AddFilesToZipTaskParameters extends AbstractZipTaskParameters {
private List<File> filesToAdd;
private ZipParameters zipParameters;
public AddFilesToZipTaskParameters(List<File> filesToAdd, ZipParameters zipParameters, Charset charset) {
super(charset);
this.filesToAdd = filesToAdd;
this.zipParameters = zipParameters;
}
}
}

View File

@@ -0,0 +1,86 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.AddFolderToZipTask.AddFolderToZipTaskParameters;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import static net.lingala.zip4j.util.FileUtils.getFilesInDirectoryRecursive;
public class AddFolderToZipTask extends AbstractAddFileToZipTask<AddFolderToZipTaskParameters> {
public AddFolderToZipTask(ZipModel zipModel, char[] password, HeaderWriter headerWriter, AsyncTaskParameters asyncTaskParameters) {
super(zipModel, password, headerWriter, asyncTaskParameters);
}
@Override
protected void executeTask(AddFolderToZipTaskParameters taskParameters, ProgressMonitor progressMonitor)
throws IOException {
List<File> filesToAdd = getFilesToAdd(taskParameters);
setDefaultFolderPath(taskParameters);
addFilesToZip(filesToAdd, progressMonitor, taskParameters.zipParameters, taskParameters.charset);
}
@Override
protected long calculateTotalWork(AddFolderToZipTaskParameters taskParameters) throws ZipException {
List<File> filesToAdd = getFilesInDirectoryRecursive(taskParameters.folderToAdd,
taskParameters.zipParameters.isReadHiddenFiles(),
taskParameters.zipParameters.isReadHiddenFolders(),
taskParameters.zipParameters.getExcludeFileFilter());
if (taskParameters.zipParameters.isIncludeRootFolder()) {
filesToAdd.add(taskParameters.folderToAdd);
}
return calculateWorkForFiles(filesToAdd, taskParameters.zipParameters);
}
private void setDefaultFolderPath(AddFolderToZipTaskParameters taskParameters) throws IOException {
String rootFolderPath;
File folderToAdd = taskParameters.folderToAdd;
if (taskParameters.zipParameters.isIncludeRootFolder()) {
File parentFile = folderToAdd.getCanonicalFile().getParentFile();
if (parentFile == null) {
rootFolderPath = folderToAdd.getCanonicalPath();
} else {
rootFolderPath = folderToAdd.getCanonicalFile().getParentFile().getCanonicalPath();
}
} else {
rootFolderPath = folderToAdd.getCanonicalPath();
}
taskParameters.zipParameters.setDefaultFolderPath(rootFolderPath);
}
private List<File> getFilesToAdd(AddFolderToZipTaskParameters taskParameters) throws ZipException {
List<File> filesToAdd = getFilesInDirectoryRecursive(taskParameters.folderToAdd,
taskParameters.zipParameters.isReadHiddenFiles(),
taskParameters.zipParameters.isReadHiddenFolders(),
taskParameters.zipParameters.getExcludeFileFilter());
if (taskParameters.zipParameters.isIncludeRootFolder()) {
filesToAdd.add(taskParameters.folderToAdd);
}
return filesToAdd;
}
public static class AddFolderToZipTaskParameters extends AbstractZipTaskParameters {
private File folderToAdd;
private ZipParameters zipParameters;
public AddFolderToZipTaskParameters(File folderToAdd, ZipParameters zipParameters, Charset charset) {
super(charset);
this.folderToAdd = folderToAdd;
this.zipParameters = zipParameters;
}
}
}

View File

@@ -0,0 +1,99 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderUtil;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.io.outputstream.SplitOutputStream;
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.AddStreamToZipTask.AddStreamToZipTaskParameters;
import net.lingala.zip4j.util.Zip4jUtil;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import static net.lingala.zip4j.util.InternalZipConstants.BUFF_SIZE;
public class AddStreamToZipTask extends AbstractAddFileToZipTask<AddStreamToZipTaskParameters> {
public AddStreamToZipTask(ZipModel zipModel, char[] password, HeaderWriter headerWriter, AsyncTaskParameters asyncTaskParameters) {
super(zipModel, password, headerWriter, asyncTaskParameters);
}
@Override
protected void executeTask(AddStreamToZipTaskParameters taskParameters, ProgressMonitor progressMonitor)
throws IOException {
verifyZipParameters(taskParameters.zipParameters);
if (!Zip4jUtil.isStringNotNullAndNotEmpty(taskParameters.zipParameters.getFileNameInZip())) {
throw new ZipException("fileNameInZip has to be set in zipParameters when adding stream");
}
removeFileIfExists(getZipModel(), taskParameters.charset, taskParameters.zipParameters.getFileNameInZip(), progressMonitor);
// For streams, it is necessary to write extended local file header because of Zip standard encryption.
// If we do not write extended local file header, zip standard encryption needs a crc upfront for key,
// which cannot be calculated until we read the complete stream. If we use extended local file header,
// last modified file time is used, or current system time if not available.
taskParameters.zipParameters.setWriteExtendedLocalFileHeader(true);
if (taskParameters.zipParameters.getCompressionMethod().equals(CompressionMethod.STORE)) {
// Set some random value here. This will be updated again when closing entry
taskParameters.zipParameters.setEntrySize(0);
}
try(SplitOutputStream splitOutputStream = new SplitOutputStream(getZipModel().getZipFile(), getZipModel().getSplitLength());
ZipOutputStream zipOutputStream = initializeOutputStream(splitOutputStream, taskParameters.charset)) {
byte[] readBuff = new byte[BUFF_SIZE];
int readLen = -1;
ZipParameters zipParameters = taskParameters.zipParameters;
zipOutputStream.putNextEntry(zipParameters);
if (!zipParameters.getFileNameInZip().endsWith("/") &&
!zipParameters.getFileNameInZip().endsWith("\\")) {
while ((readLen = taskParameters.inputStream.read(readBuff)) != -1) {
zipOutputStream.write(readBuff, 0, readLen);
}
}
FileHeader fileHeader = zipOutputStream.closeEntry();
if (fileHeader.getCompressionMethod().equals(CompressionMethod.STORE)) {
updateLocalFileHeader(fileHeader, splitOutputStream);
}
}
}
@Override
protected long calculateTotalWork(AddStreamToZipTaskParameters taskParameters) {
return 0;
}
private void removeFileIfExists(ZipModel zipModel, Charset charset, String fileNameInZip, ProgressMonitor progressMonitor)
throws ZipException {
FileHeader fileHeader = HeaderUtil.getFileHeader(zipModel, fileNameInZip);
if (fileHeader != null) {
removeFile(fileHeader, progressMonitor, charset);
}
}
public static class AddStreamToZipTaskParameters extends AbstractZipTaskParameters {
private InputStream inputStream;
private ZipParameters zipParameters;
public AddStreamToZipTaskParameters(InputStream inputStream, ZipParameters zipParameters, Charset charset) {
super(charset);
this.inputStream = inputStream;
this.zipParameters = zipParameters;
}
}
}

View File

@@ -0,0 +1,84 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.progress.ProgressMonitor;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
public abstract class AsyncZipTask<T> {
private ProgressMonitor progressMonitor;
private boolean runInThread;
private ExecutorService executorService;
public AsyncZipTask(AsyncTaskParameters asyncTaskParameters) {
this.progressMonitor = asyncTaskParameters.progressMonitor;
this.runInThread = asyncTaskParameters.runInThread;
this.executorService = asyncTaskParameters.executorService;
}
public void execute(T taskParameters) throws ZipException {
progressMonitor.fullReset();
progressMonitor.setState(ProgressMonitor.State.BUSY);
progressMonitor.setCurrentTask(getTask());
if (runInThread) {
long totalWorkToBeDone = calculateTotalWork(taskParameters);
progressMonitor.setTotalWork(totalWorkToBeDone);
executorService.execute(() -> {
try {
performTaskWithErrorHandling(taskParameters, progressMonitor);
} catch (ZipException e) {
//Do nothing. Exception will be passed through progress monitor
} finally {
executorService.shutdown();
}
});
} else {
performTaskWithErrorHandling(taskParameters, progressMonitor);
}
}
private void performTaskWithErrorHandling(T taskParameters, ProgressMonitor progressMonitor) throws ZipException {
try {
executeTask(taskParameters, progressMonitor);
progressMonitor.endProgressMonitor();
} catch (ZipException e) {
progressMonitor.endProgressMonitor(e);
throw e;
} catch (Exception e) {
progressMonitor.endProgressMonitor(e);
throw new ZipException(e);
}
}
protected void verifyIfTaskIsCancelled() throws ZipException {
if (!progressMonitor.isCancelAllTasks()) {
return;
}
progressMonitor.setResult(ProgressMonitor.Result.CANCELLED);
progressMonitor.setState(ProgressMonitor.State.READY);
throw new ZipException("Task cancelled", ZipException.Type.TASK_CANCELLED_EXCEPTION);
}
protected abstract void executeTask(T taskParameters, ProgressMonitor progressMonitor) throws IOException;
protected abstract long calculateTotalWork(T taskParameters) throws ZipException;
protected abstract ProgressMonitor.Task getTask();
public static class AsyncTaskParameters {
private ProgressMonitor progressMonitor;
private boolean runInThread;
private ExecutorService executorService;
public AsyncTaskParameters(ExecutorService executorService, boolean runInThread, ProgressMonitor progressMonitor) {
this.executorService = executorService;
this.runInThread = runInThread;
this.progressMonitor = progressMonitor;
}
}
}

View File

@@ -0,0 +1,83 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.io.inputstream.SplitInputStream;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.ExtractAllFilesTask.ExtractAllFilesTaskParameters;
import net.lingala.zip4j.util.UnzipUtil;
import java.io.IOException;
import java.nio.charset.Charset;
import static net.lingala.zip4j.headers.HeaderUtil.getTotalUncompressedSizeOfAllFileHeaders;
public class ExtractAllFilesTask extends AbstractExtractFileTask<ExtractAllFilesTaskParameters> {
private char[] password;
private SplitInputStream splitInputStream;
public ExtractAllFilesTask(ZipModel zipModel, char[] password, AsyncTaskParameters asyncTaskParameters) {
super(zipModel, asyncTaskParameters);
this.password = password;
}
@Override
protected void executeTask(ExtractAllFilesTaskParameters taskParameters, ProgressMonitor progressMonitor)
throws IOException {
try (ZipInputStream zipInputStream = prepareZipInputStream(taskParameters.charset)) {
for (FileHeader fileHeader : getZipModel().getCentralDirectory().getFileHeaders()) {
if (fileHeader.getFileName().startsWith("__MACOSX")) {
progressMonitor.updateWorkCompleted(fileHeader.getUncompressedSize());
continue;
}
splitInputStream.prepareExtractionForFileHeader(fileHeader);
extractFile(zipInputStream, fileHeader, taskParameters.outputPath, null, progressMonitor);
verifyIfTaskIsCancelled();
}
} finally {
if (splitInputStream != null) {
splitInputStream.close();
}
}
}
@Override
protected long calculateTotalWork(ExtractAllFilesTaskParameters taskParameters) {
return getTotalUncompressedSizeOfAllFileHeaders(getZipModel().getCentralDirectory().getFileHeaders());
}
private ZipInputStream prepareZipInputStream(Charset charset) throws IOException {
splitInputStream = UnzipUtil.createSplitInputStream(getZipModel());
FileHeader fileHeader = getFirstFileHeader(getZipModel());
if (fileHeader != null) {
splitInputStream.prepareExtractionForFileHeader(fileHeader);
}
return new ZipInputStream(splitInputStream, password, charset);
}
private FileHeader getFirstFileHeader(ZipModel zipModel) {
if (zipModel.getCentralDirectory() == null
|| zipModel.getCentralDirectory().getFileHeaders() == null
|| zipModel.getCentralDirectory().getFileHeaders().size() == 0) {
return null;
}
return zipModel.getCentralDirectory().getFileHeaders().get(0);
}
public static class ExtractAllFilesTaskParameters extends AbstractZipTaskParameters {
private String outputPath;
public ExtractAllFilesTaskParameters(String outputPath, Charset charset) {
super(charset);
this.outputPath = outputPath;
}
}
}

View File

@@ -0,0 +1,99 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.io.inputstream.SplitInputStream;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.ExtractFileTask.ExtractFileTaskParameters;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.UnzipUtil;
import net.lingala.zip4j.util.Zip4jUtil;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import static net.lingala.zip4j.headers.HeaderUtil.getFileHeadersUnderDirectory;
import static net.lingala.zip4j.headers.HeaderUtil.getTotalUncompressedSizeOfAllFileHeaders;
public class ExtractFileTask extends AbstractExtractFileTask<ExtractFileTaskParameters> {
private char[] password;
private SplitInputStream splitInputStream;
public ExtractFileTask(ZipModel zipModel, char[] password, AsyncTaskParameters asyncTaskParameters) {
super(zipModel, asyncTaskParameters);
this.password = password;
}
@Override
protected void executeTask(ExtractFileTaskParameters taskParameters, ProgressMonitor progressMonitor)
throws IOException {
try(ZipInputStream zipInputStream = createZipInputStream(taskParameters.fileHeader, taskParameters.charset)) {
List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileHeader);
for (FileHeader fileHeader : fileHeadersUnderDirectory) {
String newFileName = determineNewFileName(taskParameters.newFileName, taskParameters.fileHeader, fileHeader);
extractFile(zipInputStream, fileHeader, taskParameters.outputPath, newFileName, progressMonitor);
}
} finally {
if (splitInputStream != null) {
splitInputStream.close();
}
}
}
@Override
protected long calculateTotalWork(ExtractFileTaskParameters taskParameters) {
List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileHeader);
return getTotalUncompressedSizeOfAllFileHeaders(fileHeadersUnderDirectory);
}
private List<FileHeader> getFileHeadersToExtract(FileHeader rootFileHeader) {
if (!rootFileHeader.isDirectory()) {
return Collections.singletonList(rootFileHeader);
}
return getFileHeadersUnderDirectory(
getZipModel().getCentralDirectory().getFileHeaders(), rootFileHeader);
}
private ZipInputStream createZipInputStream(FileHeader fileHeader, Charset charset) throws IOException {
splitInputStream = UnzipUtil.createSplitInputStream(getZipModel());
splitInputStream.prepareExtractionForFileHeader(fileHeader);
return new ZipInputStream(splitInputStream, password, charset);
}
private String determineNewFileName(String newFileName, FileHeader fileHeaderToExtract, FileHeader fileHeaderBeingExtracted) {
if (!Zip4jUtil.isStringNotNullAndNotEmpty(newFileName)) {
return newFileName;
}
if (!fileHeaderToExtract.isDirectory()) {
return newFileName;
}
String fileSeparator = InternalZipConstants.ZIP_FILE_SEPARATOR;
if (newFileName.endsWith(InternalZipConstants.ZIP_FILE_SEPARATOR)) {
fileSeparator = "";
}
return fileHeaderBeingExtracted.getFileName().replaceFirst(fileHeaderToExtract.getFileName(),
newFileName + fileSeparator);
}
public static class ExtractFileTaskParameters extends AbstractZipTaskParameters {
private String outputPath;
private FileHeader fileHeader;
private String newFileName;
public ExtractFileTaskParameters(String outputPath, FileHeader fileHeader, String newFileName, Charset charset) {
super(charset);
this.outputPath = outputPath;
this.fileHeader = fileHeader;
this.newFileName = newFileName;
}
}
}

View File

@@ -0,0 +1,200 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderSignature;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.model.EndOfCentralDirectoryRecord;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.Zip64EndOfCentralDirectoryLocator;
import net.lingala.zip4j.model.Zip64EndOfCentralDirectoryRecord;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.enums.RandomAccessFileMode;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.MergeSplitZipFileTask.MergeSplitZipFileTaskParameters;
import net.lingala.zip4j.util.RawIO;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.List;
import static net.lingala.zip4j.util.FileUtils.copyFile;
public class MergeSplitZipFileTask extends AsyncZipTask<MergeSplitZipFileTaskParameters> {
private ZipModel zipModel;
private RawIO rawIO = new RawIO();
public MergeSplitZipFileTask(ZipModel zipModel, AsyncTaskParameters asyncTaskParameters) {
super(asyncTaskParameters);
this.zipModel = zipModel;
}
@Override
protected void executeTask(MergeSplitZipFileTaskParameters taskParameters, ProgressMonitor progressMonitor) throws IOException {
if (!zipModel.isSplitArchive()) {
ZipException e = new ZipException("archive not a split zip file");
progressMonitor.endProgressMonitor(e);
throw e;
}
try (OutputStream outputStream = new FileOutputStream(taskParameters.outputZipFile)) {
long totalBytesWritten = 0;
int totalNumberOfSplitFiles = zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk();
if (totalNumberOfSplitFiles <= 0) {
throw new ZipException("zip archive not a split zip file");
}
int splitSignatureOverhead = 0;
for (int i = 0; i <= totalNumberOfSplitFiles; i++) {
try (RandomAccessFile randomAccessFile = createSplitZipFileStream(zipModel, i)) {
int start = 0;
long end = randomAccessFile.length();
if (i == 0) {
if (rawIO.readIntLittleEndian(randomAccessFile) == HeaderSignature.SPLIT_ZIP.getValue()) {
splitSignatureOverhead = 4;
start = 4;
} else {
randomAccessFile.seek(0);
}
}
if (i == totalNumberOfSplitFiles) {
end = zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory();
}
copyFile(randomAccessFile, outputStream, start, end, progressMonitor);
totalBytesWritten += (end - start);
updateFileHeaderOffsetsForIndex(zipModel.getCentralDirectory().getFileHeaders(),
i == 0 ? 0 : totalBytesWritten, i, splitSignatureOverhead);
verifyIfTaskIsCancelled();
}
}
updateHeadersForMergeSplitFileAction(zipModel, totalBytesWritten, outputStream, taskParameters.charset);
progressMonitor.endProgressMonitor();
} catch (CloneNotSupportedException e) {
throw new ZipException(e);
}
}
@Override
protected long calculateTotalWork(MergeSplitZipFileTaskParameters taskParameters) {
if (!zipModel.isSplitArchive()) {
return 0;
}
long totalSize = 0;
for (int i = 0; i <= zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk(); i++) {
totalSize += getNextSplitZipFile(zipModel, i).length();
}
return totalSize;
}
private void updateFileHeaderOffsetsForIndex(List<FileHeader> fileHeaders, long offsetToAdd, int index,
int splitSignatureOverhead) {
for (FileHeader fileHeader : fileHeaders) {
if (fileHeader.getDiskNumberStart() == index) {
fileHeader.setOffsetLocalHeader(fileHeader.getOffsetLocalHeader() + offsetToAdd - splitSignatureOverhead);
fileHeader.setDiskNumberStart(0);
}
}
}
private File getNextSplitZipFile(ZipModel zipModel, int partNumber) {
if (partNumber == zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk()) {
return zipModel.getZipFile();
}
String splitZipExtension = ".z0";
if (partNumber >= 9) {
splitZipExtension = ".z";
}
String rootZipFile = zipModel.getZipFile().getPath();
String nextSplitZipFileName = zipModel.getZipFile().getPath().substring(0, rootZipFile.lastIndexOf("."))
+ splitZipExtension + (partNumber + 1);
return new File(nextSplitZipFileName);
}
private RandomAccessFile createSplitZipFileStream(ZipModel zipModel, int partNumber) throws FileNotFoundException {
File splitFile = getNextSplitZipFile(zipModel, partNumber);
return new RandomAccessFile(splitFile, RandomAccessFileMode.READ.getValue());
}
private void updateHeadersForMergeSplitFileAction(ZipModel zipModel, long totalBytesWritten,
OutputStream outputStream, Charset charset)
throws IOException, CloneNotSupportedException {
ZipModel newZipModel = (ZipModel) zipModel.clone();
newZipModel.getEndOfCentralDirectoryRecord().setOffsetOfStartOfCentralDirectory(totalBytesWritten);
updateSplitZipModel(newZipModel, totalBytesWritten);
HeaderWriter headerWriter = new HeaderWriter();
headerWriter.finalizeZipFileWithoutValidations(newZipModel, outputStream, charset);
}
private void updateSplitZipModel(ZipModel zipModel, long totalFileSize) {
zipModel.setSplitArchive(false);
updateSplitEndCentralDirectory(zipModel);
if (zipModel.isZip64Format()) {
updateSplitZip64EndCentralDirLocator(zipModel, totalFileSize);
updateSplitZip64EndCentralDirRec(zipModel, totalFileSize);
}
}
private void updateSplitEndCentralDirectory(ZipModel zipModel) {
int numberOfFileHeaders = zipModel.getCentralDirectory().getFileHeaders().size();
EndOfCentralDirectoryRecord endOfCentralDirectoryRecord = zipModel.getEndOfCentralDirectoryRecord();
endOfCentralDirectoryRecord.setNumberOfThisDisk(0);
endOfCentralDirectoryRecord.setNumberOfThisDiskStartOfCentralDir(0);
endOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectory(numberOfFileHeaders);
endOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(numberOfFileHeaders);
}
private void updateSplitZip64EndCentralDirLocator(ZipModel zipModel, long totalFileSize) {
if (zipModel.getZip64EndOfCentralDirectoryLocator() == null) {
return;
}
Zip64EndOfCentralDirectoryLocator zip64EndOfCentralDirectoryLocator = zipModel
.getZip64EndOfCentralDirectoryLocator();
zip64EndOfCentralDirectoryLocator.setNumberOfDiskStartOfZip64EndOfCentralDirectoryRecord(0);
zip64EndOfCentralDirectoryLocator.setOffsetZip64EndOfCentralDirectoryRecord(
zip64EndOfCentralDirectoryLocator.getOffsetZip64EndOfCentralDirectoryRecord() + totalFileSize);
zip64EndOfCentralDirectoryLocator.setTotalNumberOfDiscs(1);
}
private void updateSplitZip64EndCentralDirRec(ZipModel zipModel, long totalFileSize) {
if (zipModel.getZip64EndOfCentralDirectoryRecord() == null) {
return;
}
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectoryRecord = zipModel.getZip64EndOfCentralDirectoryRecord();
zip64EndOfCentralDirectoryRecord.setNumberOfThisDisk(0);
zip64EndOfCentralDirectoryRecord.setNumberOfThisDiskStartOfCentralDirectory(0);
zip64EndOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(
zipModel.getEndOfCentralDirectoryRecord().getTotalNumberOfEntriesInCentralDirectory());
zip64EndOfCentralDirectoryRecord.setOffsetStartCentralDirectoryWRTStartDiskNumber(
zip64EndOfCentralDirectoryRecord.getOffsetStartCentralDirectoryWRTStartDiskNumber() + totalFileSize);
}
@Override
protected ProgressMonitor.Task getTask() {
return ProgressMonitor.Task.MERGE_ZIP_FILES;
}
public static class MergeSplitZipFileTaskParameters extends AbstractZipTaskParameters {
private File outputZipFile;
public MergeSplitZipFileTaskParameters(File outputZipFile, Charset charset) {
super(charset);
this.outputZipFile = outputZipFile;
}
}
}

View File

@@ -0,0 +1,152 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderUtil;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.io.outputstream.SplitOutputStream;
import net.lingala.zip4j.model.EndOfCentralDirectoryRecord;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.enums.RandomAccessFileMode;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.RemoveFilesFromZipTask.RemoveFilesFromZipTaskParameters;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
public class RemoveFilesFromZipTask extends AbstractModifyFileTask<RemoveFilesFromZipTaskParameters> {
private ZipModel zipModel;
private HeaderWriter headerWriter;
public RemoveFilesFromZipTask(ZipModel zipModel, HeaderWriter headerWriter, AsyncTaskParameters asyncTaskParameters) {
super(asyncTaskParameters);
this.zipModel = zipModel;
this.headerWriter = headerWriter;
}
@Override
protected void executeTask(RemoveFilesFromZipTaskParameters taskParameters, ProgressMonitor progressMonitor)
throws IOException {
if (zipModel.isSplitArchive()) {
throw new ZipException("This is a split archive. Zip file format does not allow updating split/spanned files");
}
List<String> entriesToRemove = filterNonExistingEntries(taskParameters.filesToRemove);
if (entriesToRemove.isEmpty()) {
return;
}
File temporaryZipFile = getTemporaryFile(zipModel.getZipFile().getPath());
boolean successFlag = false;
try (SplitOutputStream outputStream = new SplitOutputStream(temporaryZipFile);
RandomAccessFile inputStream = new RandomAccessFile(zipModel.getZipFile(), RandomAccessFileMode.READ.getValue())){
long currentFileCopyPointer = 0;
List<FileHeader> sortedFileHeaders = cloneAndSortFileHeadersByOffset(zipModel.getCentralDirectory().getFileHeaders());
for (FileHeader fileHeader : sortedFileHeaders) {
long lengthOfCurrentEntry = getOffsetOfNextEntry(sortedFileHeaders, fileHeader, zipModel) - outputStream.getFilePointer();
if (shouldEntryBeRemoved(fileHeader, entriesToRemove)) {
updateHeaders(sortedFileHeaders, fileHeader, lengthOfCurrentEntry);
if (!zipModel.getCentralDirectory().getFileHeaders().remove(fileHeader)) {
throw new ZipException("Could not remove entry from list of central directory headers");
}
currentFileCopyPointer += lengthOfCurrentEntry;
} else {
// copy complete entry without any changes
currentFileCopyPointer += super.copyFile(inputStream, outputStream, currentFileCopyPointer, lengthOfCurrentEntry, progressMonitor);
}
verifyIfTaskIsCancelled();
}
headerWriter.finalizeZipFile(zipModel, outputStream, taskParameters.charset);
successFlag = true;
} finally {
cleanupFile(successFlag, zipModel.getZipFile(), temporaryZipFile);
}
}
@Override
protected long calculateTotalWork(RemoveFilesFromZipTaskParameters taskParameters) {
return zipModel.getZipFile().length();
}
private List<String> filterNonExistingEntries(List<String> filesToRemove) throws ZipException {
List<String> filteredFilesToRemove = new ArrayList<>();
for (String fileToRemove : filesToRemove) {
if (HeaderUtil.getFileHeader(zipModel, fileToRemove) != null) {
filteredFilesToRemove.add(fileToRemove);
}
}
return filteredFilesToRemove;
}
private boolean shouldEntryBeRemoved(FileHeader fileHeaderToBeChecked, List<String> fileNamesToBeRemoved) {
for (String fileNameToBeRemoved : fileNamesToBeRemoved) {
if (fileHeaderToBeChecked.getFileName().startsWith(fileNameToBeRemoved)) {
return true;
}
}
return false;
}
private void updateHeaders(List<FileHeader> sortedFileHeaders, FileHeader fileHeaderThatWasRemoved, long offsetToSubtract) throws ZipException {
updateOffsetsForAllSubsequentFileHeaders(sortedFileHeaders, zipModel, fileHeaderThatWasRemoved, negate(offsetToSubtract));
EndOfCentralDirectoryRecord endOfCentralDirectoryRecord = zipModel.getEndOfCentralDirectoryRecord();
endOfCentralDirectoryRecord.setOffsetOfStartOfCentralDirectory(
endOfCentralDirectoryRecord.getOffsetOfStartOfCentralDirectory() - offsetToSubtract);
endOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectory(
endOfCentralDirectoryRecord.getTotalNumberOfEntriesInCentralDirectory() - 1);
if (endOfCentralDirectoryRecord.getTotalNumberOfEntriesInCentralDirectoryOnThisDisk() > 0) {
endOfCentralDirectoryRecord.setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(
endOfCentralDirectoryRecord.getTotalNumberOfEntriesInCentralDirectoryOnThisDisk() - 1);
}
if (zipModel.isZip64Format()) {
zipModel.getZip64EndOfCentralDirectoryRecord().setOffsetStartCentralDirectoryWRTStartDiskNumber(
zipModel.getZip64EndOfCentralDirectoryRecord().getOffsetStartCentralDirectoryWRTStartDiskNumber() - offsetToSubtract);
zipModel.getZip64EndOfCentralDirectoryRecord().setTotalNumberOfEntriesInCentralDirectoryOnThisDisk(
zipModel.getZip64EndOfCentralDirectoryRecord().getTotalNumberOfEntriesInCentralDirectory() - 1);
zipModel.getZip64EndOfCentralDirectoryLocator().setOffsetZip64EndOfCentralDirectoryRecord(
zipModel.getZip64EndOfCentralDirectoryLocator().getOffsetZip64EndOfCentralDirectoryRecord() - offsetToSubtract);
}
}
private long negate(long val) {
if (val == Long.MIN_VALUE) {
throw new ArithmeticException("long overflow");
}
return -val;
}
@Override
protected ProgressMonitor.Task getTask() {
return ProgressMonitor.Task.REMOVE_ENTRY;
}
public static class RemoveFilesFromZipTaskParameters extends AbstractZipTaskParameters {
private List<String> filesToRemove;
public RemoveFilesFromZipTaskParameters(List<String> filesToRemove, Charset charset) {
super(charset);
this.filesToRemove = filesToRemove;
}
}
}

View File

@@ -0,0 +1,205 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderUtil;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.io.outputstream.SplitOutputStream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.enums.RandomAccessFileMode;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.RawIO;
import net.lingala.zip4j.util.Zip4jUtil;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RenameFilesTask extends AbstractModifyFileTask<RenameFilesTask.RenameFilesTaskParameters> {
private ZipModel zipModel;
private HeaderWriter headerWriter;
private RawIO rawIO;
private Charset charset;
public RenameFilesTask(ZipModel zipModel, HeaderWriter headerWriter, RawIO rawIO, Charset charset, AsyncTaskParameters asyncTaskParameters) {
super(asyncTaskParameters);
this.zipModel = zipModel;
this.headerWriter = headerWriter;
this.rawIO = rawIO;
this.charset = charset;
}
@Override
protected void executeTask(RenameFilesTaskParameters taskParameters, ProgressMonitor progressMonitor) throws IOException {
Map<String, String> fileNamesMap = filterNonExistingEntriesAndAddSeparatorIfNeeded(taskParameters.fileNamesMap);
if (fileNamesMap.size() == 0) {
return;
}
File temporaryFile = getTemporaryFile(zipModel.getZipFile().getPath());
boolean successFlag = false;
try(RandomAccessFile inputStream = new RandomAccessFile(zipModel.getZipFile(), RandomAccessFileMode.WRITE.getValue());
SplitOutputStream outputStream = new SplitOutputStream(temporaryFile)) {
long currentFileCopyPointer = 0;
// Maintain a different list to iterate, so that when the file name is changed in the central directory
// we still have access to the original file names. If iterating on the original list from central directory,
// it might be that a file name has changed because of other file name, ex: if a directory name has to be changed
// and the file is part of that directory, by the time the file has to be changed, its name might have changed
// when changing the name of the directory. There is some overhead with this approach, but is safer.
List<FileHeader> sortedFileHeaders = cloneAndSortFileHeadersByOffset(zipModel.getCentralDirectory().getFileHeaders());
for (FileHeader fileHeader : sortedFileHeaders) {
Map.Entry<String, String> fileNameMapForThisEntry = getCorrespondingEntryFromMap(fileHeader, fileNamesMap);
progressMonitor.setFileName(fileHeader.getFileName());
long lengthToCopy = getOffsetOfNextEntry(sortedFileHeaders, fileHeader, zipModel) - outputStream.getFilePointer();
if (fileNameMapForThisEntry == null) {
// copy complete entry without any changes
currentFileCopyPointer += copyFile(inputStream, outputStream, currentFileCopyPointer, lengthToCopy, progressMonitor);
} else {
String newFileName = getNewFileName(fileNameMapForThisEntry.getValue(), fileNameMapForThisEntry.getKey(), fileHeader.getFileName());
byte[] newFileNameBytes = newFileName.getBytes(charset);
int headersOffset = newFileNameBytes.length - fileHeader.getFileNameLength();
currentFileCopyPointer = copyEntryAndChangeFileName(newFileNameBytes, fileHeader, currentFileCopyPointer, lengthToCopy,
inputStream, outputStream, progressMonitor);
updateHeadersInZipModel(sortedFileHeaders, fileHeader, newFileName, newFileNameBytes, headersOffset);
}
verifyIfTaskIsCancelled();
}
headerWriter.finalizeZipFile(zipModel, outputStream, charset);
successFlag = true;
} finally {
cleanupFile(successFlag, zipModel.getZipFile(), temporaryFile);
}
}
@Override
protected long calculateTotalWork(RenameFilesTaskParameters taskParameters) {
return zipModel.getZipFile().length();
}
@Override
protected ProgressMonitor.Task getTask() {
return ProgressMonitor.Task.RENAME_FILE;
}
private long copyEntryAndChangeFileName(byte[] newFileNameBytes, FileHeader fileHeader, long start, long totalLengthOfEntry,
RandomAccessFile inputStream, OutputStream outputStream,
ProgressMonitor progressMonitor) throws IOException {
long currentFileCopyPointer = start;
currentFileCopyPointer += copyFile(inputStream, outputStream, currentFileCopyPointer, 26, progressMonitor); // 26 is offset until file name length
rawIO.writeShortLittleEndian(outputStream, newFileNameBytes.length);
currentFileCopyPointer += 2; // length of file name length
currentFileCopyPointer += copyFile(inputStream, outputStream, currentFileCopyPointer, 2, progressMonitor); // 2 is for length of extra field length
outputStream.write(newFileNameBytes);
currentFileCopyPointer += fileHeader.getFileNameLength();
long remainingLengthToCopy = totalLengthOfEntry - (currentFileCopyPointer - start);
currentFileCopyPointer += copyFile(inputStream, outputStream, currentFileCopyPointer,
remainingLengthToCopy, progressMonitor);
return currentFileCopyPointer;
}
private Map.Entry<String, String> getCorrespondingEntryFromMap(FileHeader fileHeaderToBeChecked, Map<String,
String> fileNamesMap) {
for (Map.Entry<String, String> fileHeaderToBeRenamed : fileNamesMap.entrySet()) {
if (fileHeaderToBeChecked.getFileName().startsWith(fileHeaderToBeRenamed.getKey())) {
return fileHeaderToBeRenamed;
}
}
return null;
}
private void updateHeadersInZipModel(List<FileHeader> sortedFileHeaders, FileHeader fileHeader, String newFileName,
byte[] newFileNameBytes, int headersOffset) throws ZipException {
FileHeader fileHeaderToBeChanged = HeaderUtil.getFileHeader(zipModel, fileHeader.getFileName());
if (fileHeaderToBeChanged == null) {
// If this is the case, then the file name in the header that was passed to this method was already changed.
// In theory, should never be here.
throw new ZipException("could not find any header with name: " + fileHeader.getFileName());
}
fileHeaderToBeChanged.setFileName(newFileName);
fileHeaderToBeChanged.setFileNameLength(newFileNameBytes.length);
updateOffsetsForAllSubsequentFileHeaders(sortedFileHeaders, zipModel, fileHeaderToBeChanged, headersOffset);
zipModel.getEndOfCentralDirectoryRecord().setOffsetOfStartOfCentralDirectory(
zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory() + headersOffset);
if (zipModel.isZip64Format()) {
zipModel.getZip64EndOfCentralDirectoryRecord().setOffsetStartCentralDirectoryWRTStartDiskNumber(
zipModel.getZip64EndOfCentralDirectoryRecord().getOffsetStartCentralDirectoryWRTStartDiskNumber() + headersOffset
);
zipModel.getZip64EndOfCentralDirectoryLocator().setOffsetZip64EndOfCentralDirectoryRecord(
zipModel.getZip64EndOfCentralDirectoryLocator().getOffsetZip64EndOfCentralDirectoryRecord() + headersOffset
);
}
}
private Map<String, String> filterNonExistingEntriesAndAddSeparatorIfNeeded(Map<String, String> inputFileNamesMap) throws ZipException {
Map<String, String> fileNamesMapToBeChanged = new HashMap<>();
for (Map.Entry<String, String> allNamesToBeChanged : inputFileNamesMap.entrySet()) {
if (!Zip4jUtil.isStringNotNullAndNotEmpty(allNamesToBeChanged.getKey())) {
continue;
}
FileHeader fileHeaderToBeChanged = HeaderUtil.getFileHeader(zipModel, allNamesToBeChanged.getKey());
if (fileHeaderToBeChanged != null) {
if (fileHeaderToBeChanged.isDirectory() && !allNamesToBeChanged.getValue().endsWith(InternalZipConstants.ZIP_FILE_SEPARATOR)) {
fileNamesMapToBeChanged.put(allNamesToBeChanged.getKey(), allNamesToBeChanged.getValue() + InternalZipConstants.ZIP_FILE_SEPARATOR);
} else {
fileNamesMapToBeChanged.put(allNamesToBeChanged.getKey(), allNamesToBeChanged.getValue());
}
}
}
return fileNamesMapToBeChanged;
}
private String getNewFileName(String newFileName, String oldFileName, String fileNameFromHeaderToBeChanged) throws ZipException {
if (fileNameFromHeaderToBeChanged.equals(oldFileName)) {
return newFileName;
} else if (fileNameFromHeaderToBeChanged.startsWith(oldFileName)) {
String fileNameWithoutOldName = fileNameFromHeaderToBeChanged.substring(oldFileName.length());
return newFileName + fileNameWithoutOldName;
}
// Should never be here.
// If here by any chance, it means that the file header was marked as to-be-modified, even when the file names do not
// match. Logic in the method getCorrespondingEntryFromMap() has to be checked
throw new ZipException("old file name was neither an exact match nor a partial match");
}
public static class RenameFilesTaskParameters {
private Map<String, String> fileNamesMap;
public RenameFilesTaskParameters(Map<String, String> fileNamesMap) {
this.fileNamesMap = fileNamesMap;
}
}
}

View File

@@ -0,0 +1,63 @@
package net.lingala.zip4j.tasks;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderWriter;
import net.lingala.zip4j.io.outputstream.SplitOutputStream;
import net.lingala.zip4j.model.EndOfCentralDirectoryRecord;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.SetCommentTask.SetCommentTaskTaskParameters;
import java.io.IOException;
import java.nio.charset.Charset;
public class SetCommentTask extends AsyncZipTask<SetCommentTaskTaskParameters> {
private ZipModel zipModel;
public SetCommentTask(ZipModel zipModel, AsyncTaskParameters asyncTaskParameters) {
super(asyncTaskParameters);
this.zipModel = zipModel;
}
@Override
protected void executeTask(SetCommentTaskTaskParameters taskParameters, ProgressMonitor progressMonitor) throws IOException {
if (taskParameters.comment == null) {
throw new ZipException("comment is null, cannot update Zip file with comment");
}
EndOfCentralDirectoryRecord endOfCentralDirectoryRecord = zipModel.getEndOfCentralDirectoryRecord();
endOfCentralDirectoryRecord.setComment(taskParameters.comment);
try (SplitOutputStream outputStream = new SplitOutputStream(zipModel.getZipFile())) {
if (zipModel.isZip64Format()) {
outputStream.seek(zipModel.getZip64EndOfCentralDirectoryRecord()
.getOffsetStartCentralDirectoryWRTStartDiskNumber());
} else {
outputStream.seek(endOfCentralDirectoryRecord.getOffsetOfStartOfCentralDirectory());
}
HeaderWriter headerWriter = new HeaderWriter();
headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream, taskParameters.charset);
}
}
@Override
protected long calculateTotalWork(SetCommentTaskTaskParameters taskParameters) {
return 0;
}
@Override
protected ProgressMonitor.Task getTask() {
return ProgressMonitor.Task.SET_COMMENT;
}
public static class SetCommentTaskTaskParameters extends AbstractZipTaskParameters {
private String comment;
public SetCommentTaskTaskParameters(String comment, Charset charset) {
super(charset);
this.comment = comment;
}
}
}

View File

@@ -0,0 +1,16 @@
package net.lingala.zip4j.util;
public class BitUtils {
public static boolean isBitSet(byte b, int pos) {
return (b & (1L << pos)) != 0;
}
public static byte setBit(byte b, int pos) {
return (byte) (b | 1 << pos);
}
public static byte unsetBit(byte b, int pos) {
return (byte) (b & ~(1 << pos));
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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 net.lingala.zip4j.util;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.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(File inputFile, ProgressMonitor progressMonitor) throws IOException {
if (inputFile == null || !inputFile.exists() || !inputFile.canRead()) {
throw new ZipException("input file is null or does not exist or cannot read. " +
"Cannot calculate CRC for the file");
}
byte[] buff = new byte[BUF_SIZE];
CRC32 crc32 = new CRC32();
try(InputStream inputStream = new FileInputStream(inputFile)) {
int readLen;
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();
}
}
}

View File

@@ -0,0 +1,552 @@
package net.lingala.zip4j.util;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ExcludeFileFilter;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.progress.ProgressMonitor;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE;
import static java.nio.file.attribute.PosixFilePermission.GROUP_READ;
import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE;
import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE;
import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ;
import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE;
import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
import static net.lingala.zip4j.util.BitUtils.isBitSet;
import static net.lingala.zip4j.util.InternalZipConstants.BUFF_SIZE;
import static net.lingala.zip4j.util.InternalZipConstants.FILE_SEPARATOR;
import static net.lingala.zip4j.util.InternalZipConstants.ZIP_FILE_SEPARATOR;
import static net.lingala.zip4j.util.Zip4jUtil.isStringNotNullAndNotEmpty;
public class FileUtils {
public static final byte[] DEFAULT_POSIX_FILE_ATTRIBUTES = new byte[] {0, 0, -128, -127}; //-rw-------
public static final byte[] DEFAULT_POSIX_FOLDER_ATTRIBUTES = new byte[] {0, 0, -128, 65}; //drw-------
public static void setFileAttributes(Path file, byte[] fileAttributes) {
if (fileAttributes == null || fileAttributes.length == 0) {
return;
}
if (isWindows()) {
applyWindowsFileAttributes(file, fileAttributes);
} else if (isMac() || isUnix()) {
applyPosixFileAttributes(file, fileAttributes);
}
}
public static void setFileLastModifiedTime(Path file, long lastModifiedTime) {
if (lastModifiedTime <= 0 || !Files.exists(file)) {
return;
}
try {
Files.setLastModifiedTime(file, FileTime.fromMillis(Zip4jUtil.dosToExtendedEpochTme(lastModifiedTime)));
} catch (Exception e) {
// Ignore
}
}
public static void setFileLastModifiedTimeWithoutNio(File file, long lastModifiedTime) {
file.setLastModified(Zip4jUtil.dosToExtendedEpochTme(lastModifiedTime));
}
public static byte[] getFileAttributes(File file) {
try {
if (file == null || (!Files.isSymbolicLink(file.toPath()) && !file.exists())) {
return new byte[4];
}
Path path = file.toPath();
if (isWindows()) {
return getWindowsFileAttributes(path);
} else if (isMac() || isUnix()) {
return getPosixFileAttributes(path);
} else {
return new byte[4];
}
} catch (NoSuchMethodError e) {
return new byte[4];
}
}
public static List<File> getFilesInDirectoryRecursive(File path, boolean readHiddenFiles, boolean readHiddenFolders) throws ZipException {
return getFilesInDirectoryRecursive(path, readHiddenFiles, readHiddenFolders, null);
}
public static List<File> getFilesInDirectoryRecursive(File path, boolean readHiddenFiles, boolean readHiddenFolders, ExcludeFileFilter excludedFiles)
throws ZipException {
if (path == null) {
throw new ZipException("input path is null, cannot read files in the directory");
}
List<File> result = new ArrayList<>();
File[] filesAndDirs = path.listFiles();
if (!path.isDirectory() || !path.canRead() || filesAndDirs == null) {
return result;
}
for (File file : filesAndDirs) {
if (excludedFiles != null && excludedFiles.isExcluded(file)) {
continue;
}
if (file.isHidden()) {
if (file.isDirectory()) {
if (!readHiddenFolders) {
continue;
}
} else if (!readHiddenFiles) {
continue;
}
}
result.add(file);
if (file.isDirectory()) {
result.addAll(getFilesInDirectoryRecursive(file, readHiddenFiles, readHiddenFolders, excludedFiles));
}
}
return result;
}
public static String getFileNameWithoutExtension(String fileName) {
int pos = fileName.lastIndexOf(".");
if (pos == -1) {
return fileName;
}
return fileName.substring(0, pos);
}
public static String getZipFileNameWithoutExtension(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.contains(System.getProperty("file.separator"))) {
tmpFileName = zipFile.substring(zipFile.lastIndexOf(System.getProperty("file.separator")) + 1);
}
if (tmpFileName.endsWith(".zip")) {
tmpFileName = tmpFileName.substring(0, tmpFileName.lastIndexOf("."));
}
return tmpFileName;
}
public static List<File> getSplitZipFiles(ZipModel zipModel) throws ZipException {
if (zipModel == null) {
throw new ZipException("cannot get split zip files: zipmodel is null");
}
if (zipModel.getEndOfCentralDirectoryRecord() == null) {
return null;
}
if (!zipModel.getZipFile().exists()) {
throw new ZipException("zip file does not exist");
}
List<File> splitZipFiles = new ArrayList<>();
File currZipFile = zipModel.getZipFile();
String partFile;
if (!zipModel.isSplitArchive()) {
splitZipFiles.add(currZipFile);
return splitZipFiles;
}
int numberOfThisDisk = zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk();
if (numberOfThisDisk == 0) {
splitZipFiles.add(currZipFile);
return splitZipFiles;
} else {
for (int i = 0; i <= numberOfThisDisk; i++) {
if (i == numberOfThisDisk) {
splitZipFiles.add(zipModel.getZipFile());
} else {
String fileExt = ".z0";
if (i >= 9) {
fileExt = ".z";
}
partFile = (currZipFile.getName().contains("."))
? currZipFile.getPath().substring(0, currZipFile.getPath().lastIndexOf(".")) : currZipFile.getPath();
partFile = partFile + fileExt + (i + 1);
splitZipFiles.add(new File(partFile));
}
}
}
return splitZipFiles;
}
public static String getRelativeFileName(File fileToAdd, ZipParameters zipParameters) throws ZipException {
String fileName;
try {
String fileCanonicalPath = fileToAdd.getCanonicalPath();
if (isStringNotNullAndNotEmpty(zipParameters.getDefaultFolderPath())) {
File rootFolderFile = new File(zipParameters.getDefaultFolderPath());
String rootFolderFileRef = rootFolderFile.getCanonicalPath();
if (!rootFolderFileRef.endsWith(FILE_SEPARATOR)) {
rootFolderFileRef += FILE_SEPARATOR;
}
String tmpFileName;
if (isSymbolicLink(fileToAdd)) {
String rootPath = new File(fileToAdd.getParentFile().getCanonicalFile().getPath() + File.separator + fileToAdd.getCanonicalFile().getName()).getPath();
tmpFileName = rootPath.substring(rootFolderFileRef.length());
} else {
tmpFileName = fileCanonicalPath.substring(rootFolderFileRef.length());
}
if (tmpFileName.startsWith(System.getProperty("file.separator"))) {
tmpFileName = tmpFileName.substring(1);
}
File tmpFile = new File(fileCanonicalPath);
if (tmpFile.isDirectory()) {
tmpFileName = tmpFileName.replaceAll("\\\\", "/");
tmpFileName += ZIP_FILE_SEPARATOR;
} else {
String bkFileName = tmpFileName.substring(0, tmpFileName.lastIndexOf(tmpFile.getName()));
bkFileName = bkFileName.replaceAll("\\\\", "/");
tmpFileName = bkFileName + getNameOfFileInZip(tmpFile, zipParameters.getFileNameInZip());
}
fileName = tmpFileName;
} else {
File relFile = new File(fileCanonicalPath);
fileName = getNameOfFileInZip(relFile, zipParameters.getFileNameInZip());
if (relFile.isDirectory()) {
fileName += ZIP_FILE_SEPARATOR;
}
}
} catch (IOException e) {
throw new ZipException(e);
}
String rootFolderNameInZip = zipParameters.getRootFolderNameInZip();
if (Zip4jUtil.isStringNotNullAndNotEmpty(rootFolderNameInZip)) {
if (!rootFolderNameInZip.endsWith("\\") && !rootFolderNameInZip.endsWith("/")) {
rootFolderNameInZip = rootFolderNameInZip + InternalZipConstants.FILE_SEPARATOR;
}
rootFolderNameInZip = rootFolderNameInZip.replaceAll("\\\\", ZIP_FILE_SEPARATOR);
fileName = rootFolderNameInZip + fileName;
}
return fileName;
}
private static String getNameOfFileInZip(File fileToAdd, String fileNameInZip) throws IOException {
if (isStringNotNullAndNotEmpty(fileNameInZip)) {
return fileNameInZip;
}
if (isSymbolicLink(fileToAdd)) {
return fileToAdd.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString();
}
return fileToAdd.getName();
}
public static boolean isZipEntryDirectory(String fileNameInZip) {
return fileNameInZip.endsWith("/") || fileNameInZip.endsWith("\\");
}
public static void copyFile(RandomAccessFile randomAccessFile, OutputStream outputStream, long start, long end,
ProgressMonitor progressMonitor) throws ZipException {
if (start < 0 || end < 0 || start > end) {
throw new ZipException("invalid offsets");
}
if (start == end) {
return;
}
try {
randomAccessFile.seek(start);
int readLen;
byte[] buff;
long bytesRead = 0;
long bytesToRead = end - start;
if ((end - start) < BUFF_SIZE) {
buff = new byte[(int) bytesToRead];
} else {
buff = new byte[BUFF_SIZE];
}
while ((readLen = randomAccessFile.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);
}
}
public static void assertFilesExist(List<File> files, ZipParameters.SymbolicLinkAction symLinkAction) throws ZipException {
for (File file : files) {
if (isSymbolicLink(file)) {
// If symlink is INCLUDE_LINK_ONLY, and if the above condition is true, it means that the link exists and there
// will be no need to check for link existence explicitly, check only for target file existence if required
if (symLinkAction.equals(ZipParameters.SymbolicLinkAction.INCLUDE_LINK_AND_LINKED_FILE)
|| symLinkAction.equals(ZipParameters.SymbolicLinkAction.INCLUDE_LINKED_FILE_ONLY)) {
assertSymbolicLinkTargetExists(file);
}
} else {
assertFileExists(file);
}
}
}
public static boolean isNumberedSplitFile(File file) {
return file.getName().endsWith(InternalZipConstants.SEVEN_ZIP_SPLIT_FILE_EXTENSION_PATTERN);
}
public static String getFileExtension(File file) {
String fileName = file.getName();
if (!fileName.contains(".")) {
return "";
}
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
/**
* A helper method to retrieve all split files which are of the format split by 7-zip, i.e, .zip.001, .zip.002, etc.
* This method also sorts all the files by their split part
* @param firstNumberedFile - first split file
* @return sorted list of split files. Returns an empty list if no files of that pattern are found in the current directory
*/
public static File[] getAllSortedNumberedSplitFiles(File firstNumberedFile) {
String zipFileNameWithoutExtension = FileUtils.getFileNameWithoutExtension(firstNumberedFile.getName());
File[] allSplitFiles = firstNumberedFile.getParentFile().listFiles((dir, name) -> name.startsWith(zipFileNameWithoutExtension + "."));
if(allSplitFiles == null) {
return new File[0];
}
Arrays.sort(allSplitFiles);
return allSplitFiles;
}
public static String getNextNumberedSplitFileCounterAsExtension(int index) {
return "." + getExtensionZerosPrefix(index) + (index + 1);
}
public static boolean isSymbolicLink(File file) {
try {
return Files.isSymbolicLink(file.toPath());
} catch (Exception | Error e) {
return false;
}
}
public static String readSymbolicLink(File file) {
try {
return Files.readSymbolicLink(file.toPath()).toString();
} catch (Exception | Error e) {
return "";
}
}
public static byte[] getDefaultFileAttributes(boolean isDirectory) {
byte[] permissions = new byte[4];
if (isUnix() || isMac()) {
if (isDirectory) {
System.arraycopy(DEFAULT_POSIX_FOLDER_ATTRIBUTES, 0, permissions, 0, permissions.length);
} else {
System.arraycopy(DEFAULT_POSIX_FILE_ATTRIBUTES, 0, permissions, 0, permissions.length);
}
}
return permissions;
}
public static boolean isWindows() {
String os = System.getProperty("os.name").toLowerCase();
return (os.contains("win"));
}
public static boolean isMac() {
String os = System.getProperty("os.name").toLowerCase();
return (os.contains("mac"));
}
public static boolean isUnix() {
String os = System.getProperty("os.name").toLowerCase();
return (os.contains("nux"));
}
private static String getExtensionZerosPrefix(int index) {
if (index < 9) {
return "00";
} else if (index < 99) {
return "0";
} else {
return "";
}
}
private static void applyWindowsFileAttributes(Path file, byte[] fileAttributes) {
if (fileAttributes[0] == 0) {
// No file attributes defined in the archive
return;
}
DosFileAttributeView fileAttributeView = Files.getFileAttributeView(file, DosFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
try {
fileAttributeView.setReadOnly(isBitSet(fileAttributes[0], 0));
fileAttributeView.setHidden(isBitSet(fileAttributes[0], 1));
fileAttributeView.setSystem(isBitSet(fileAttributes[0], 2));
fileAttributeView.setArchive(isBitSet(fileAttributes[0], 5));
} catch (IOException e) {
//Ignore
}
}
private static void applyPosixFileAttributes(Path file, byte[] fileAttributes) {
if (fileAttributes[2] == 0 && fileAttributes[3] == 0) {
// No file attributes defined
return;
}
try {
Set<PosixFilePermission> posixFilePermissions = new HashSet<>();
addIfBitSet(fileAttributes[3], 0, posixFilePermissions, PosixFilePermission.OWNER_READ);
addIfBitSet(fileAttributes[2], 7, posixFilePermissions, PosixFilePermission.OWNER_WRITE);
addIfBitSet(fileAttributes[2], 6, posixFilePermissions, PosixFilePermission.OWNER_EXECUTE);
addIfBitSet(fileAttributes[2], 5, posixFilePermissions, PosixFilePermission.GROUP_READ);
addIfBitSet(fileAttributes[2], 4, posixFilePermissions, PosixFilePermission.GROUP_WRITE);
addIfBitSet(fileAttributes[2], 3, posixFilePermissions, PosixFilePermission.GROUP_EXECUTE);
addIfBitSet(fileAttributes[2], 2, posixFilePermissions, PosixFilePermission.OTHERS_READ);
addIfBitSet(fileAttributes[2], 1, posixFilePermissions, PosixFilePermission.OTHERS_WRITE);
addIfBitSet(fileAttributes[2], 0, posixFilePermissions, PosixFilePermission.OTHERS_EXECUTE);
Files.setPosixFilePermissions(file, posixFilePermissions);
} catch (IOException e) {
// Ignore
}
}
private static byte[] getWindowsFileAttributes(Path file) {
byte[] fileAttributes = new byte[4];
try {
DosFileAttributeView dosFileAttributeView = Files.getFileAttributeView(file, DosFileAttributeView.class,
LinkOption.NOFOLLOW_LINKS);
DosFileAttributes dosFileAttributes = dosFileAttributeView.readAttributes();
byte windowsAttribute = 0;
windowsAttribute = setBitIfApplicable(dosFileAttributes.isReadOnly(), windowsAttribute, 0);
windowsAttribute = setBitIfApplicable(dosFileAttributes.isHidden(), windowsAttribute, 1);
windowsAttribute = setBitIfApplicable(dosFileAttributes.isSystem(), windowsAttribute, 2);
windowsAttribute = setBitIfApplicable(dosFileAttributes.isArchive(), windowsAttribute, 5);
fileAttributes[0] = windowsAttribute;
} catch (IOException e) {
// ignore
}
return fileAttributes;
}
private static void assertFileExists(File file) throws ZipException {
if (!file.exists()) {
throw new ZipException("File does not exist: " + file);
}
}
private static void assertSymbolicLinkTargetExists(File file) throws ZipException {
if (!file.exists()) {
throw new ZipException("Symlink target '" + readSymbolicLink(file) + "' does not exist for link '" + file + "'");
}
}
private static byte[] getPosixFileAttributes(Path file) {
byte[] fileAttributes = new byte[4];
try {
PosixFileAttributeView posixFileAttributeView = Files.getFileAttributeView(file, PosixFileAttributeView.class,
LinkOption.NOFOLLOW_LINKS);
Set<PosixFilePermission> posixFilePermissions = posixFileAttributeView.readAttributes().permissions();
fileAttributes[3] = setBitIfApplicable(Files.isRegularFile(file), fileAttributes[3], 7);
fileAttributes[3] = setBitIfApplicable(Files.isDirectory(file), fileAttributes[3], 6);
fileAttributes[3] = setBitIfApplicable(Files.isSymbolicLink(file), fileAttributes[3], 5);
fileAttributes[3] = setBitIfApplicable(posixFilePermissions.contains(OWNER_READ), fileAttributes[3], 0);
fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(OWNER_WRITE), fileAttributes[2], 7);
fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(OWNER_EXECUTE), fileAttributes[2], 6);
fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(GROUP_READ), fileAttributes[2], 5);
fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(GROUP_WRITE), fileAttributes[2], 4);
fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(GROUP_EXECUTE), fileAttributes[2], 3);
fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(OTHERS_READ), fileAttributes[2], 2);
fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(OTHERS_WRITE), fileAttributes[2], 1);
fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(OTHERS_EXECUTE), fileAttributes[2], 0);
} catch (IOException e) {
// Ignore
}
return fileAttributes;
}
private static byte setBitIfApplicable(boolean applicable, byte b, int pos) {
if (applicable) {
b = BitUtils.setBit(b, pos);
}
return b;
}
private static void addIfBitSet(byte b, int pos, Set<PosixFilePermission> posixFilePermissions,
PosixFilePermission posixFilePermissionToAdd) {
if (isBitSet(b, pos)) {
posixFilePermissions.add(posixFilePermissionToAdd);
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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 net.lingala.zip4j.util;
import java.io.File;
import java.nio.charset.Charset;
public final class InternalZipConstants {
private InternalZipConstants() {
}
public static final int ENDHDR = 22; // END header size
public static final int STD_DEC_HDR_SIZE = 12;
//AES Constants
public static final int AES_AUTH_LENGTH = 10;
public static final int AES_BLOCK_SIZE = 16;
public static final int AES_EXTRA_DATA_RECORD_SIZE = 11;
public static final String AES_MAC_ALGORITHM = "HmacSHA1";
public static final String AES_HASH_CHARSET = "ISO-8859-1";
public static final int AES_HASH_ITERATIONS = 1000;
public static final int AES_PASSWORD_VERIFIER_LENGTH = 2;
public static final int MIN_SPLIT_LENGTH = 65536;
public static final long ZIP_64_SIZE_LIMIT = 4294967295L;
public static final int ZIP_64_NUMBER_OF_ENTRIES_LIMIT = 65535;
public static final int BUFF_SIZE = 1024 * 4;
// 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 String ZIP_STANDARD_CHARSET = "Cp437";
public static final String FILE_SEPARATOR = File.separator;
public static final String ZIP_FILE_SEPARATOR = "/";
public static final int MAX_ALLOWED_ZIP_COMMENT_LENGTH = 0xFFFF;
public static final Charset CHARSET_UTF_8 = Charset.forName("UTF-8");
public static final String SEVEN_ZIP_SPLIT_FILE_EXTENSION_PATTERN = ".zip.001";
}

View File

@@ -0,0 +1,164 @@
/*
* 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 net.lingala.zip4j.util;
import net.lingala.zip4j.exception.ZipException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
public class RawIO {
private byte[] shortBuff = new byte[2];
private byte[] intBuff = new byte[4];
private byte[] longBuff = new byte[8];
public long readLongLittleEndian(RandomAccessFile randomAccessFile) throws IOException {
randomAccessFile.readFully(longBuff);
return readLongLittleEndian(longBuff, 0);
}
public long readLongLittleEndian(RandomAccessFile randomAccessFile, int readLen) throws IOException {
resetBytes(longBuff);
randomAccessFile.readFully(longBuff, 0, readLen);
return readLongLittleEndian(longBuff, 0);
}
public long readLongLittleEndian(InputStream inputStream) throws IOException {
readFully(inputStream, longBuff, longBuff.length);
return readLongLittleEndian(longBuff, 0);
}
public long readLongLittleEndian(InputStream inputStream, int readLen) throws IOException {
resetBytes(longBuff);
readFully(inputStream, longBuff, readLen);
return readLongLittleEndian(longBuff, 0);
}
public long readLongLittleEndian(byte[] array, int pos) {
if (array.length - pos < 8) {
resetBytes(longBuff);
}
System.arraycopy(array, pos, longBuff, 0, array.length < 8 ? array.length - pos : 8);
long temp = 0;
temp |= longBuff[7] & 0xff;
temp <<= 8;
temp |= longBuff[6] & 0xff;
temp <<= 8;
temp |= longBuff[5] & 0xff;
temp <<= 8;
temp |= longBuff[4] & 0xff;
temp <<= 8;
temp |= longBuff[3] & 0xff;
temp <<= 8;
temp |= longBuff[2] & 0xff;
temp <<= 8;
temp |= longBuff[1] & 0xff;
temp <<= 8;
temp |= longBuff[0] & 0xff;
return temp;
}
public int readIntLittleEndian(RandomAccessFile randomAccessFile) throws IOException {
randomAccessFile.readFully(intBuff);
return readIntLittleEndian(intBuff);
}
public int readIntLittleEndian(InputStream inputStream) throws IOException {
readFully(inputStream, intBuff, 4);
return readIntLittleEndian(intBuff);
}
public int readIntLittleEndian(byte[] b) {
return readIntLittleEndian(b, 0);
}
public int readIntLittleEndian(byte[] b, int pos) {
return ((b[pos] & 0xff) | (b[1 + pos] & 0xff) << 8)
| ((b[2 + pos] & 0xff) | (b[3 + pos] & 0xff) << 8) << 16;
}
public int readShortLittleEndian(RandomAccessFile randomAccessFile) throws IOException {
randomAccessFile.readFully(shortBuff);
return readShortLittleEndian(shortBuff, 0);
}
public int readShortLittleEndian(InputStream inputStream) throws IOException {
readFully(inputStream, shortBuff, shortBuff.length);
return readShortLittleEndian(shortBuff, 0);
}
public int readShortLittleEndian(byte[] buff, int position) {
return (buff[position] & 0xff) | (buff[1 + position] & 0xff) << 8;
}
public void writeShortLittleEndian(OutputStream outputStream, int value) throws IOException {
writeShortLittleEndian(shortBuff, 0, value);
outputStream.write(shortBuff);
}
public void writeShortLittleEndian(byte[] array, int pos, int value) {
array[pos + 1] = (byte) (value >>> 8);
array[pos] = (byte) (value & 0xFF);
}
public void writeIntLittleEndian(OutputStream outputStream, int value) throws IOException {
writeIntLittleEndian(intBuff, 0, value);
outputStream.write(intBuff);
}
public 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 void writeLongLittleEndian(OutputStream outputStream, long value) throws IOException {
writeLongLittleEndian(longBuff, 0, value);
outputStream.write(longBuff);
}
public 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);
}
private void readFully(InputStream inputStream, byte[] buff, int readLen) throws IOException {
int actualReadLength = Zip4jUtil.readFully(inputStream, buff, 0, readLen);
if (actualReadLength != readLen) {
throw new ZipException("Could not fill buffer");
}
}
private void resetBytes(byte[] b) {
for(int i = 0; i < b.length; i++) {
b[i] = 0;
}
}
}

View File

@@ -0,0 +1,66 @@
package net.lingala.zip4j.util;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.io.inputstream.NumberedSplitInputStream;
import net.lingala.zip4j.io.inputstream.SplitInputStream;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.io.inputstream.ZipStandardSplitInputStream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static net.lingala.zip4j.util.FileUtils.setFileAttributes;
import static net.lingala.zip4j.util.FileUtils.setFileLastModifiedTime;
import static net.lingala.zip4j.util.FileUtils.setFileLastModifiedTimeWithoutNio;
public class UnzipUtil {
public static ZipInputStream createZipInputStream(ZipModel zipModel, FileHeader fileHeader, char[] password)
throws IOException {
SplitInputStream splitInputStream = null;
try {
splitInputStream = createSplitInputStream(zipModel);
splitInputStream.prepareExtractionForFileHeader(fileHeader);
ZipInputStream zipInputStream = new ZipInputStream(splitInputStream, password);
if (zipInputStream.getNextEntry(fileHeader) == null) {
throw new ZipException("Could not locate local file header for corresponding file header");
}
return zipInputStream;
} catch (IOException e) {
if (splitInputStream != null) {
splitInputStream.close();
}
throw e;
}
}
public static void applyFileAttributes(FileHeader fileHeader, File file) {
try {
Path path = file.toPath();
setFileAttributes(path, fileHeader.getExternalFileAttributes());
setFileLastModifiedTime(path, fileHeader.getLastModifiedTime());
} catch (NoSuchMethodError e) {
setFileLastModifiedTimeWithoutNio(file, fileHeader.getLastModifiedTime());
}
}
public static SplitInputStream createSplitInputStream(ZipModel zipModel) throws IOException {
File zipFile = zipModel.getZipFile();
if (zipFile.getName().endsWith(InternalZipConstants.SEVEN_ZIP_SPLIT_FILE_EXTENSION_PATTERN)) {
return new NumberedSplitInputStream(zipModel.getZipFile(), true,
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk());
}
return new ZipStandardSplitInputStream(zipModel.getZipFile(), zipModel.isSplitArchive(),
zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk());
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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 net.lingala.zip4j.util;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.LocalFileHeader;
import net.lingala.zip4j.model.enums.CompressionMethod;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
public class Zip4jUtil {
private static final long DOSTIME_BEFORE_1980 = (1 << 21) | (1 << 16);
private static final int MAX_RAW_READ_FULLY_RETRY_ATTEMPTS = 15;
public static boolean isStringNotNullAndNotEmpty(String str) {
return str != null && str.trim().length() > 0;
}
public static boolean createDirectoryIfNotExists(File file) throws ZipException {
if (file == null) {
throw new ZipException("output path is null");
}
if (file.exists()) {
if (!file.isDirectory()) {
throw new ZipException("output directory is not valid");
}
} else {
if (!file.mkdirs()) {
throw new ZipException("Cannot create output directories");
}
}
return true;
}
public static long epochToExtendedDosTime(long time) {
if (time < 0) {
return DOSTIME_BEFORE_1980;
}
long dostime = epochToDosTime(time);
return (dostime != DOSTIME_BEFORE_1980)
? dostime + ((time % 2000) << 32)
: DOSTIME_BEFORE_1980;
}
private static long epochToDosTime(long time) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time);
int year = cal.get(Calendar.YEAR);
if (year < 1980) {
return DOSTIME_BEFORE_1980;
}
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;
}
public static long dosToExtendedEpochTme(long dosTime) {
long time = dosToEpochTime(dosTime);
return time + (dosTime >> 32);
}
private static long dosToEpochTime(long dosTime) {
int sec = (int) ((dosTime << 1) & 0x3e);
int min = (int) ((dosTime >> 5) & 0x3f);
int hrs = (int) ((dosTime >> 11) & 0x1f);
int day = (int) ((dosTime >> 16) & 0x1f);
int mon = (int) (((dosTime >> 21) & 0xf) - 1);
int year = (int) (((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 byte[] convertCharArrayToByteArray(char[] charArray) {
byte[] bytes = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++) {
bytes[i] = (byte) charArray[i];
}
return bytes;
}
public static CompressionMethod getCompressionMethod(LocalFileHeader localFileHeader) {
if (localFileHeader.getCompressionMethod() != CompressionMethod.AES_INTERNAL_ONLY) {
return localFileHeader.getCompressionMethod();
}
if (localFileHeader.getAesExtraDataRecord() == null) {
throw new RuntimeException("AesExtraDataRecord not present in local header for aes encrypted data");
}
return localFileHeader.getAesExtraDataRecord().getCompressionMethod();
}
public static int readFully(InputStream inputStream, byte[] bufferToReadInto) throws IOException {
int readLen = inputStream.read(bufferToReadInto);
if (readLen != bufferToReadInto.length) {
readLen = readUntilBufferIsFull(inputStream, bufferToReadInto, readLen);
if (readLen != bufferToReadInto.length) {
throw new IOException("Cannot read fully into byte buffer");
}
}
return readLen;
}
public static int readFully(InputStream inputStream, byte[] b, int offset, int length) throws IOException {
int numberOfBytesRead = 0;
if (offset < 0) {
throw new IllegalArgumentException("Negative offset");
}
if (length < 0) {
throw new IllegalArgumentException("Negative length");
}
if (length == 0) {
return 0;
}
if (offset + length > b.length) {
throw new IllegalArgumentException("Length greater than buffer size");
}
while (numberOfBytesRead != length) {
int currentReadLength = inputStream.read(b, offset + numberOfBytesRead, length - numberOfBytesRead);
if (currentReadLength == -1) {
if (numberOfBytesRead == 0) {
return -1;
}
return numberOfBytesRead;
}
numberOfBytesRead += currentReadLength;
}
return numberOfBytesRead;
}
private static int readUntilBufferIsFull(InputStream inputStream, byte[] bufferToReadInto, int readLength)
throws IOException {
int remainingLength = bufferToReadInto.length - readLength;
int loopReadLength = 0;
int retryAttempt = 1; // first attempt is already done before this method is called
while (readLength < bufferToReadInto.length
&& loopReadLength != -1
&& retryAttempt < MAX_RAW_READ_FULLY_RETRY_ATTEMPTS) {
loopReadLength = inputStream.read(bufferToReadInto, readLength, remainingLength);
if (loopReadLength > 0) {
readLength += loopReadLength;
remainingLength -= loopReadLength;
}
retryAttempt++;
}
return readLength;
}
}

Some files were not shown because too many files have changed in this diff Show More