Added miltipart archives support
This commit is contained in:
12
README.md
12
README.md
@@ -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
|
||||
@@ -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": {
|
||||
|
||||
11
plugin.xml
11
plugin.xml
@@ -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"/>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
1061
src/android/net/lingala/zip4j/ZipFile.java
Executable file
1061
src/android/net/lingala/zip4j/ZipFile.java
Executable file
File diff suppressed because it is too large
Load Diff
92
src/android/net/lingala/zip4j/crypto/AESDecrypter.java
Executable file
92
src/android/net/lingala/zip4j/crypto/AESDecrypter.java
Executable 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();
|
||||
}
|
||||
}
|
||||
152
src/android/net/lingala/zip4j/crypto/AESEncrypter.java
Normal file
152
src/android/net/lingala/zip4j/crypto/AESEncrypter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
99
src/android/net/lingala/zip4j/crypto/AesCipherUtil.java
Normal file
99
src/android/net/lingala/zip4j/crypto/AesCipherUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
src/android/net/lingala/zip4j/crypto/Decrypter.java
Executable file
25
src/android/net/lingala/zip4j/crypto/Decrypter.java
Executable 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;
|
||||
|
||||
}
|
||||
27
src/android/net/lingala/zip4j/crypto/Encrypter.java
Executable file
27
src/android/net/lingala/zip4j/crypto/Encrypter.java
Executable 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;
|
||||
|
||||
}
|
||||
70
src/android/net/lingala/zip4j/crypto/PBKDF2/BinTools.java
Executable file
70
src/android/net/lingala/zip4j/crypto/PBKDF2/BinTools.java
Executable 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 + "'");
|
||||
}
|
||||
}
|
||||
79
src/android/net/lingala/zip4j/crypto/PBKDF2/MacBasedPRF.java
Executable file
79
src/android/net/lingala/zip4j/crypto/PBKDF2/MacBasedPRF.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/android/net/lingala/zip4j/crypto/PBKDF2/PBKDF2Engine.java
Executable file
159
src/android/net/lingala/zip4j/crypto/PBKDF2/PBKDF2Engine.java
Executable 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;
|
||||
}
|
||||
}
|
||||
51
src/android/net/lingala/zip4j/crypto/PBKDF2/PBKDF2HexFormatter.java
Executable file
51
src/android/net/lingala/zip4j/crypto/PBKDF2/PBKDF2HexFormatter.java
Executable 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;
|
||||
}
|
||||
}
|
||||
91
src/android/net/lingala/zip4j/crypto/PBKDF2/PBKDF2Parameters.java
Executable file
91
src/android/net/lingala/zip4j/crypto/PBKDF2/PBKDF2Parameters.java
Executable 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;
|
||||
}
|
||||
}
|
||||
31
src/android/net/lingala/zip4j/crypto/PBKDF2/PRF.java
Executable file
31
src/android/net/lingala/zip4j/crypto/PBKDF2/PRF.java
Executable 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();
|
||||
}
|
||||
82
src/android/net/lingala/zip4j/crypto/StandardDecrypter.java
Executable file
82
src/android/net/lingala/zip4j/crypto/StandardDecrypter.java
Executable 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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
99
src/android/net/lingala/zip4j/crypto/StandardEncrypter.java
Executable file
99
src/android/net/lingala/zip4j/crypto/StandardEncrypter.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
||||
283
src/android/net/lingala/zip4j/crypto/engine/AESEngine.java
Executable file
283
src/android/net/lingala/zip4j/crypto/engine/AESEngine.java
Executable 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};
|
||||
|
||||
}
|
||||
62
src/android/net/lingala/zip4j/crypto/engine/ZipCryptoEngine.java
Executable file
62
src/android/net/lingala/zip4j/crypto/engine/ZipCryptoEngine.java
Executable 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);
|
||||
}
|
||||
}
|
||||
62
src/android/net/lingala/zip4j/exception/ZipException.java
Executable file
62
src/android/net/lingala/zip4j/exception/ZipException.java
Executable 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
|
||||
}
|
||||
}
|
||||
176
src/android/net/lingala/zip4j/headers/FileHeaderFactory.java
Normal file
176
src/android/net/lingala/zip4j/headers/FileHeaderFactory.java
Normal 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;
|
||||
}
|
||||
}
|
||||
753
src/android/net/lingala/zip4j/headers/HeaderReader.java
Executable file
753
src/android/net/lingala/zip4j/headers/HeaderReader.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/android/net/lingala/zip4j/headers/HeaderSignature.java
Normal file
26
src/android/net/lingala/zip4j/headers/HeaderSignature.java
Normal 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;
|
||||
}
|
||||
}
|
||||
118
src/android/net/lingala/zip4j/headers/HeaderUtil.java
Normal file
118
src/android/net/lingala/zip4j/headers/HeaderUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
696
src/android/net/lingala/zip4j/headers/HeaderWriter.java
Executable file
696
src/android/net/lingala/zip4j/headers/HeaderWriter.java
Executable 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;
|
||||
}
|
||||
}
|
||||
18
src/android/net/lingala/zip4j/headers/VersionMadeBy.java
Normal file
18
src/android/net/lingala/zip4j/headers/VersionMadeBy.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.lingala.zip4j.io.inputstream;
|
||||
|
||||
class StoreInputStream extends DecompressedInputStream {
|
||||
|
||||
public StoreInputStream(CipherInputStream cipherInputStream) {
|
||||
super(cipherInputStream);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
320
src/android/net/lingala/zip4j/io/inputstream/ZipInputStream.java
Executable file
320
src/android/net/lingala/zip4j/io/inputstream/ZipInputStream.java
Executable 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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
76
src/android/net/lingala/zip4j/io/outputstream/CipherOutputStream.java
Executable file
76
src/android/net/lingala/zip4j/io/outputstream/CipherOutputStream.java
Executable 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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
70
src/android/net/lingala/zip4j/io/outputstream/DeflaterOutputStream.java
Executable file
70
src/android/net/lingala/zip4j/io/outputstream/DeflaterOutputStream.java
Executable 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.lingala.zip4j.io.outputstream;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface OutputStreamWithSplitZipSupport {
|
||||
|
||||
long getFilePointer() throws IOException;
|
||||
|
||||
int getCurrentSplitFileCounter();
|
||||
}
|
||||
212
src/android/net/lingala/zip4j/io/outputstream/SplitOutputStream.java
Executable file
212
src/android/net/lingala/zip4j/io/outputstream/SplitOutputStream.java
Executable 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.lingala.zip4j.io.outputstream;
|
||||
|
||||
class StoreOutputStream extends CompressedOutputStream {
|
||||
|
||||
public StoreOutputStream(CipherOutputStream cipherOutputStream) {
|
||||
super(cipherOutputStream);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
228
src/android/net/lingala/zip4j/io/outputstream/ZipOutputStream.java
Executable file
228
src/android/net/lingala/zip4j/io/outputstream/ZipOutputStream.java
Executable 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("\\");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
80
src/android/net/lingala/zip4j/model/AESExtraDataRecord.java
Executable file
80
src/android/net/lingala/zip4j/model/AESExtraDataRecord.java
Executable 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;
|
||||
}
|
||||
}
|
||||
199
src/android/net/lingala/zip4j/model/AbstractFileHeader.java
Normal file
199
src/android/net/lingala/zip4j/model/AbstractFileHeader.java
Normal 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());
|
||||
}
|
||||
}
|
||||
40
src/android/net/lingala/zip4j/model/ArchiveExtraDataRecord.java
Executable file
40
src/android/net/lingala/zip4j/model/ArchiveExtraDataRecord.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
||||
44
src/android/net/lingala/zip4j/model/CentralDirectory.java
Executable file
44
src/android/net/lingala/zip4j/model/CentralDirectory.java
Executable 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
49
src/android/net/lingala/zip4j/model/DataDescriptor.java
Executable file
49
src/android/net/lingala/zip4j/model/DataDescriptor.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
||||
40
src/android/net/lingala/zip4j/model/DigitalSignature.java
Executable file
40
src/android/net/lingala/zip4j/model/DigitalSignature.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
||||
103
src/android/net/lingala/zip4j/model/EndOfCentralDirectoryRecord.java
Executable file
103
src/android/net/lingala/zip4j/model/EndOfCentralDirectoryRecord.java
Executable 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.lingala.zip4j.model;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface ExcludeFileFilter {
|
||||
|
||||
boolean isExcluded(File file);
|
||||
|
||||
}
|
||||
49
src/android/net/lingala/zip4j/model/ExtraDataRecord.java
Executable file
49
src/android/net/lingala/zip4j/model/ExtraDataRecord.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
||||
95
src/android/net/lingala/zip4j/model/FileHeader.java
Executable file
95
src/android/net/lingala/zip4j/model/FileHeader.java
Executable 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();
|
||||
}
|
||||
}
|
||||
54
src/android/net/lingala/zip4j/model/LocalFileHeader.java
Executable file
54
src/android/net/lingala/zip4j/model/LocalFileHeader.java
Executable 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;
|
||||
}
|
||||
}
|
||||
51
src/android/net/lingala/zip4j/model/Zip64EndOfCentralDirectoryLocator.java
Executable file
51
src/android/net/lingala/zip4j/model/Zip64EndOfCentralDirectoryLocator.java
Executable 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
115
src/android/net/lingala/zip4j/model/Zip64EndOfCentralDirectoryRecord.java
Executable file
115
src/android/net/lingala/zip4j/model/Zip64EndOfCentralDirectoryRecord.java
Executable 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
75
src/android/net/lingala/zip4j/model/Zip64ExtendedInfo.java
Executable file
75
src/android/net/lingala/zip4j/model/Zip64ExtendedInfo.java
Executable 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
16
src/android/net/lingala/zip4j/model/ZipHeader.java
Normal file
16
src/android/net/lingala/zip4j/model/ZipHeader.java
Normal 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;
|
||||
}
|
||||
}
|
||||
163
src/android/net/lingala/zip4j/model/ZipModel.java
Executable file
163
src/android/net/lingala/zip4j/model/ZipModel.java
Executable 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();
|
||||
}
|
||||
}
|
||||
419
src/android/net/lingala/zip4j/model/ZipParameters.java
Executable file
419
src/android/net/lingala/zip4j/model/ZipParameters.java
Executable 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
44
src/android/net/lingala/zip4j/model/enums/AesVersion.java
Normal file
44
src/android/net/lingala/zip4j/model/enums/AesVersion.java
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
162
src/android/net/lingala/zip4j/progress/ProgressMonitor.java
Executable file
162
src/android/net/lingala/zip4j/progress/ProgressMonitor.java
Executable 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
177
src/android/net/lingala/zip4j/tasks/AbstractExtractFileTask.java
Normal file
177
src/android/net/lingala/zip4j/tasks/AbstractExtractFileTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
120
src/android/net/lingala/zip4j/tasks/AbstractModifyFileTask.java
Normal file
120
src/android/net/lingala/zip4j/tasks/AbstractModifyFileTask.java
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
49
src/android/net/lingala/zip4j/tasks/AddFilesToZipTask.java
Normal file
49
src/android/net/lingala/zip4j/tasks/AddFilesToZipTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/android/net/lingala/zip4j/tasks/AddFolderToZipTask.java
Normal file
86
src/android/net/lingala/zip4j/tasks/AddFolderToZipTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
99
src/android/net/lingala/zip4j/tasks/AddStreamToZipTask.java
Normal file
99
src/android/net/lingala/zip4j/tasks/AddStreamToZipTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/android/net/lingala/zip4j/tasks/AsyncZipTask.java
Normal file
84
src/android/net/lingala/zip4j/tasks/AsyncZipTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/android/net/lingala/zip4j/tasks/ExtractAllFilesTask.java
Normal file
83
src/android/net/lingala/zip4j/tasks/ExtractAllFilesTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
99
src/android/net/lingala/zip4j/tasks/ExtractFileTask.java
Normal file
99
src/android/net/lingala/zip4j/tasks/ExtractFileTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
200
src/android/net/lingala/zip4j/tasks/MergeSplitZipFileTask.java
Normal file
200
src/android/net/lingala/zip4j/tasks/MergeSplitZipFileTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
152
src/android/net/lingala/zip4j/tasks/RemoveFilesFromZipTask.java
Normal file
152
src/android/net/lingala/zip4j/tasks/RemoveFilesFromZipTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/android/net/lingala/zip4j/tasks/RenameFilesTask.java
Normal file
205
src/android/net/lingala/zip4j/tasks/RenameFilesTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/android/net/lingala/zip4j/tasks/SetCommentTask.java
Normal file
63
src/android/net/lingala/zip4j/tasks/SetCommentTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/android/net/lingala/zip4j/util/BitUtils.java
Normal file
16
src/android/net/lingala/zip4j/util/BitUtils.java
Normal 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));
|
||||
}
|
||||
}
|
||||
60
src/android/net/lingala/zip4j/util/CrcUtil.java
Executable file
60
src/android/net/lingala/zip4j/util/CrcUtil.java
Executable 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
552
src/android/net/lingala/zip4j/util/FileUtils.java
Normal file
552
src/android/net/lingala/zip4j/util/FileUtils.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/android/net/lingala/zip4j/util/InternalZipConstants.java
Executable file
66
src/android/net/lingala/zip4j/util/InternalZipConstants.java
Executable 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";
|
||||
}
|
||||
164
src/android/net/lingala/zip4j/util/RawIO.java
Executable file
164
src/android/net/lingala/zip4j/util/RawIO.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/android/net/lingala/zip4j/util/UnzipUtil.java
Executable file
66
src/android/net/lingala/zip4j/util/UnzipUtil.java
Executable 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());
|
||||
}
|
||||
|
||||
}
|
||||
191
src/android/net/lingala/zip4j/util/Zip4jUtil.java
Executable file
191
src/android/net/lingala/zip4j/util/Zip4jUtil.java
Executable 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
Reference in New Issue
Block a user