FileUtils.java
package net.lingala.zip4j.util;
import net.lingala.zip4j.exception.ZipException;
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.FilenameFilter;
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.model.ZipParameters.SymbolicLinkAction.INCLUDE_LINKED_FILE_ONLY;
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.util.BitUtils.isBitSet;
import static net.lingala.zip4j.util.BitUtils.setBit;
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, -92, -127}; //-rw-r--r--
public static final byte[] DEFAULT_POSIX_FOLDER_ATTRIBUTES = new byte[] {0, 0, -19, 65}; //drwxr-xr-x
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, ZipParameters zipParameters)
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 (zipParameters.getExcludeFileFilter() != null && zipParameters.getExcludeFileFilter().isExcluded(file)) {
continue;
}
if (file.isHidden() && !zipParameters.isReadHiddenFiles()) {
continue;
}
result.add(file);
ZipParameters.SymbolicLinkAction symbolicLinkAction = zipParameters.getSymbolicLinkAction();
boolean isSymLink = isSymbolicLink(file);
// If a symlink's target is a directory, file.isDirectory is true. Only check if file is a directory is file is
// not a symlink.
if ((isSymLink && !INCLUDE_LINK_ONLY.equals(symbolicLinkAction))
|| (!isSymLink && file.isDirectory())) {
result.addAll(getFilesInDirectoryRecursive(file,zipParameters));
}
}
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 {
if (!fileToAdd.getCanonicalFile().getPath().startsWith(rootFolderFileRef)) {
tmpFileName = fileToAdd.getCanonicalFile().getParentFile().getName() + FILE_SEPARATOR + fileToAdd.getCanonicalFile().getName();
} 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("\\\\", ZIP_FILE_SEPARATOR);
tmpFileName += ZIP_FILE_SEPARATOR;
} else {
String bkFileName = tmpFileName.substring(0, tmpFileName.lastIndexOf(tmpFile.getName()));
bkFileName = bkFileName.replaceAll("\\\\", ZIP_FILE_SEPARATOR);
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;
}
if (!isStringNotNullAndNotEmpty(fileName)) {
String errorMessage = "fileName to add to zip is empty or null. fileName: '" + fileName + "' "
+ "DefaultFolderPath: '" + zipParameters.getDefaultFolderPath() + "' "
+ "FileNameInZip: " + zipParameters.getFileNameInZip();
if (isSymbolicLink(fileToAdd)) {
errorMessage += "isSymlink: true ";
}
if (Zip4jUtil.isStringNotNullAndNotEmpty(rootFolderNameInZip)) {
errorMessage = "rootFolderNameInZip: '" + rootFolderNameInZip + "' ";
}
throw new ZipException(errorMessage);
}
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, int bufferSize) 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) < bufferSize) {
buff = new byte[(int) bytesToRead];
} else {
buff = new byte[bufferSize];
}
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(INCLUDE_LINK_AND_LINKED_FILE)
|| symLinkAction.equals(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) {
final String zipFileNameWithoutExtension = FileUtils.getFileNameWithoutExtension(firstNumberedFile.getName());
File[] allSplitFiles = firstNumberedFile.getParentFile().listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return 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);
}
} else if (isWindows() && isDirectory) {
permissions[0] = setBit(permissions[0], 4);
}
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);
//IntelliJ complains that fileAttributeView can never be null. But apparently it can.
//See https://github.com/srikanth-lingala/zip4j/issues/435
//Even the javadoc of Files.getFileAttributeView says it can be null
//noinspection ConstantConditions
if (fileAttributeView == null) {
return;
}
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);
if (dosFileAttributeView == null) {
return fileAttributes;
}
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.isDirectory(), windowsAttribute, 4);
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();
boolean isSymlink = Files.isSymbolicLink(file);
if (isSymlink) {
// Mark as a regular file and not a directory if file is a symlink and even if the symlink points to a directory
fileAttributes[3] = BitUtils.setBit(fileAttributes[3], 7);
fileAttributes[3] = BitUtils.unsetBit(fileAttributes[3], 6);
} else {
fileAttributes[3] = setBitIfApplicable(Files.isRegularFile(file), fileAttributes[3], 7);
fileAttributes[3] = setBitIfApplicable(Files.isDirectory(file), fileAttributes[3], 6);
}
fileAttributes[3] = setBitIfApplicable(isSymlink, 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);
}
}
}