/*
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved.
*/
package zip4cj.util
@When[os == "Linux"]
func isLinuxOS(): Bool {true}
@When[os != "Linux"]
func isLinuxOS(): Bool {false}
@When[os == "Windows"]
func isWindowsOS(): Bool {true}
@When[os != "Windows"]
func isWindowsOS(): Bool {false}
@When[os == "macOS"]
func isMacOS(): Bool {true}
@When[os != "macOS"]
func isMacOS(): Bool {false}
func getCurrentOS(): (Bool, Bool, Bool) {
let islin = isLinuxOS()
let iswin = isWindowsOS()
let ismac = isMacOS()
if (!islin && !iswin && !ismac) {
return (false, false, false)
} else if (islin) {
return (true, false, false)
}else if (iswin) {
return (false, true, false)
}else if (ismac) {
return (false, false, true)
} else {
throw IllegalArgumentException()
}
}
let (isUnix, isWindows, isMac) = getCurrentOS()
public class FileUtils {
public static let DEFAULT_POSIX_FILE_ATTRIBUTES: Array<Byte> = [0, 0, 164, 129] //-rw-r--r--
public static let DEFAULT_POSIX_FOLDER_ATTRIBUTES: Array<Byte> = [0, 0, 237, 65] //drwxr-xr-x
public static func setFileAttributes(file: Path, fileAttributes: Array<Byte>): Unit {
if (fileAttributes.size == 0) {
return
}
if (isWindows) {
applyWindowsFileAttributes(file, fileAttributes)
} else if (isMac || isUnix) {
applyPosixFileAttributes(file, fileAttributes)
}
}
// TODO
public static func setFileLastModifiedTime(file: Path, lastModifiedTime: Int64) {
if (lastModifiedTime <= 0 || !exists(file)) {
return
}
}
// TODO
public static func setFileLastModifiedTimeWithoutNio(file: Path, lastModifiedTime: Int64) {
(file, lastModifiedTime)
return
}
public static func getFileAttributes(file: FileInfo): Array<Byte> {
try {
if ((!file.isSymbolicLink() && !exists(file.path))) {
return Array<Byte>(4, repeat: 0)
}
if (isWindows) {
return getWindowsFileAttributes(file.path)
} else if (isMac || isUnix) {
return getPosixFileAttributes(file.path)
} else {
return Array<Byte>(4, repeat: 0)
}
} catch (e: Exception) {
return Array<Byte>(4, repeat: 0)
}
}
// TODO 如果是链接文件
public static func getFilesInDirectoryRecursive(
path: Path,
zipParameters: ZipParameters,
paths!: ArrayList<Path> = ArrayList<Path>()
): ArrayList<Path> {
if (path.isEmpty() || !exists(path)) {
return ArrayList<Path>()
}
let fileInfo = FileInfo(path)
if (!fileInfo.isDirectory() || !fileInfo.canRead()) {
return paths
}
for (subPath in Directory.readFrom(path)) {
if (let Some(v) <- zipParameters.getExcludeFileFilter()) {
if (v.isExcluded(subPath.path)) {
continue
}
}
let isSymLink = subPath.isSymbolicLink()
if (isSymLink) {
paths.add(subPath.path)
if (zipParameters.getSymbolicLinkAction() != SymbolicLinkAction.INCLUDE_LINK_ONLY) {
let linkPath = SymbolicLink.readFrom(subPath.path)
getFilesInDirectoryRecursive(linkPath, zipParameters, paths: paths)
}
continue
}
if (subPath.isHidden() && !zipParameters.isReadHiddenFiles()) {
continue
}
paths.add(subPath.path)
if (!isSymLink && subPath.isDirectory()) {
getFilesInDirectoryRecursive(subPath.path, zipParameters, paths: paths)
}
}
return paths
}
public static func getFileNameWithoutExtension(fileName: String): String {
if (let Some(pos) <- fileName.lastIndexOf(".")) {
return String(fileName.toRuneArray()[0..pos])
}
return fileName
}
public static func getZipFileNameWithoutExtension(zipFile: String): String {
if (!Zip4cjUtil.isStringNotNullAndNotEmpty(zipFile)) {
throw ZipException("zip file name is empty or null, cannot determine zip file name")
}
return if (zipFile.endsWith(".zip")) {
zipFile[0..zipFile.size - 4]
} else {
zipFile
}
}
public static func getSplitZipFiles(zipModel: ZipModel): ArrayList<Path> {
if (!exists(zipModel.getZipFile())) {
throw ZipException("zip file does not exist")
}
var splitZipFiles = ArrayList<Path>()
var currZipFile = zipModel.getZipFile()
var partFile: String
if (!zipModel.isSplitArchive()) {
splitZipFiles.add(currZipFile)
return splitZipFiles
}
var numberOfThisDisk = zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk()
if (numberOfThisDisk == 0) {
splitZipFiles.add(currZipFile)
return splitZipFiles
} else {
for (i in 0..numberOfThisDisk) {
if (i == numberOfThisDisk) {
splitZipFiles.add(zipModel.getZipFile())
} else {
var fileExt = ".z0"
if (i >= 9) {
fileExt = ".z"
}
partFile = if (currZipFile.fileName.contains(".")) {
unsafe{
String.fromUtf8Unchecked(
currZipFile.toString().toArray()[0..currZipFile.toString().
lastIndexOf(".").getOrThrow()])
}
} else {
currZipFile.toString()
}
partFile = partFile + fileExt + "${(i + 1)}"
splitZipFiles.add(Path(partFile))
}
}
}
return splitZipFiles
}
public static func getRelativeFileName(fileToAdd: Path, zipParameters: ZipParameters): String {
if (fileToAdd.isEmpty()) {
return ""
} else if (!exists(fileToAdd)) {
return fileToAdd.fileName
}
var fileName: String
try {
var defaultFolderPath = match (zipParameters.getDefaultFolderPath()) {
case Some(v) => v.trim()
case None => ""
}
if (defaultFolderPath.size != 0) {
var fileCanonicalPath: Path
try {
fileCanonicalPath = canonicalize(fileToAdd)
} catch(_: Exception) {
return fileToAdd.fileName
}
var rootFolderFile = Path(defaultFolderPath)
var rootFolderFileRef = canonicalize(rootFolderFile).toString()
if (!rootFolderFileRef.endsWith(Path.Separator)) {
rootFolderFileRef = "${rootFolderFileRef}${Path.Separator}"
}
var tmpFileName: String = if (FileInfo(fileToAdd).isSymbolicLink()) {
var rootPath = "${canonicalize(fileToAdd.parent)}${Path.Separator}${canonicalize(fileToAdd).fileName}"
unsafe { String.fromUtf8Unchecked(rootPath.toArray()[rootFolderFileRef.size..])}
} else {
let canonicalizeFileToAdd = canonicalize(fileToAdd)
if (!canonicalizeFileToAdd.toString().startsWith(rootFolderFileRef)) {
"${canonicalizeFileToAdd.parent}${Path.Separator}${canonicalizeFileToAdd}"
} else {
unsafe { String.fromUtf8Unchecked(fileCanonicalPath.toString().toArray()[rootFolderFileRef.size..]) }
}
}
if (tmpFileName.startsWith(Path.Separator)) {
tmpFileName = unsafe { String.fromUtf8Unchecked(tmpFileName.toArray()[1..]) }
}
var tmpFile = fileCanonicalPath
if (FileInfo(tmpFile).isDirectory()) {
tmpFileName = "${tmpFileName.replace("\\\\", InternalZipConstants.ZIP_FILE_SEPARATOR)}${InternalZipConstants.ZIP_FILE_SEPARATOR}"
} else {
let bkFileName = unsafe {
String.fromUtf8Unchecked(
tmpFileName.toArray()[0..tmpFileName.lastIndexOf(tmpFile.fileName).getOrThrow()]
)}
tmpFileName = "${bkFileName.replace("\\\\", InternalZipConstants.ZIP_FILE_SEPARATOR)}${getNameOfFileInZip(tmpFile, zipParameters.getFileNameInZip())}"
}
fileName = tmpFileName
} else {
fileName = getNameOfFileInZip(fileToAdd, zipParameters.getFileNameInZip())
if (FileInfo(fileToAdd).isDirectory()) {
fileName += InternalZipConstants.ZIP_FILE_SEPARATOR
}
}
} catch (e: IOException) {
e.printStackTrace()
throw ZipException(e.message)
}
var rootFolderNameInZip = ""
if (let Some(v) <- zipParameters.getRootFolderNameInZip()) {
var rootFolderNameInZip = v
if (!rootFolderNameInZip.endsWith("\\") && !rootFolderNameInZip.endsWith("/")) {
rootFolderNameInZip = "${rootFolderNameInZip}${Path.Separator}"
}
fileName = "${rootFolderNameInZip.replace("\\\\", InternalZipConstants.ZIP_FILE_SEPARATOR)}${fileName}"
}
if (!Zip4cjUtil.isStringNotNullAndNotEmpty(fileName)) {
var errorMessage = "fileName to add to zip is empty or null. fileName: '${fileName}' DefaultFolderPath: '${zipParameters.getDefaultFolderPath()}' FileNameInZip: ${zipParameters.getFileNameInZip()}"
if (FileInfo(fileToAdd).isSymbolicLink()) {
errorMessage += "isSymlink: true "
}
if (Zip4cjUtil.isStringNotNullAndNotEmpty(rootFolderNameInZip)) {
errorMessage = "rootFolderNameInZip: '" + rootFolderNameInZip + "' "
}
throw ZipException(errorMessage)
}
return fileName
}
private static func getNameOfFileInZip(fileToAdd: Path, fileNameInZip: ?String): String {
if (Zip4cjUtil.isStringNotNullAndNotEmpty(fileNameInZip)) {
return fileNameInZip.getOrThrow()
}
// TODO 设置链接文件
return fileToAdd.fileName
}
public static func isZipEntryDirectory(fileNameInZip: String): Bool {
return fileNameInZip.endsWith("/") || fileNameInZip.endsWith("\\")
}
public static func copyFile(
randomAccessFile: RandomAccessFile,
outputStream: OutputStream,
start: Int64,
end: Int64,
progressMonitor: ProgressMonitor,
bufferSize: Int64
) {
if (start < 0 || end < 0 || start > end) {
throw ZipException("invalid offsets")
}
if (start == end) {
return
}
try {
randomAccessFile.seek(start)
var readLen: Int64
var buff: Array<Byte>
var bytesRead: Int64 = 0
var bytesToRead: Int64 = end - start
if ((end - start) < bufferSize) {
buff = Array<Byte>(bytesToRead, repeat: 0)
} else {
buff = Array<Byte>(bufferSize, repeat: 0)
}
readLen = randomAccessFile.read(buff)
while (readLen != -1) {
outputStream.write(buff[0..readLen])
progressMonitor.updateWorkCompleted(readLen)
if (progressMonitor.isCancelAllTasks()) {
progressMonitor.setResult(ProgressMonitorResult.CANCELLED)
return
}
bytesRead += readLen
if (bytesRead == bytesToRead) {
break
} else if (bytesRead + buff.size > bytesToRead) {
buff = Array<Byte>(bytesToRead - bytesRead, repeat: 0)
}
readLen = randomAccessFile.read(buff)
}
} catch (e: IOException) {
throw ZipException(e.message)
}
}
public static func isNumberedSplitFile(file: Path): Bool {
return file.fileName.endsWith(InternalZipConstants.SEVEN_ZIP_SPLIT_FILE_EXTENSION_PATTERN)
}
public static func getFileExtension(path: Path): String {
return path.extensionName
}
/**
* 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 func getAllSortedNumberedSplitFiles(firstNumberedFile: Path): Array<Path> {
var zipFileNameWithoutExtension = FileUtils.getFileNameWithoutExtension(firstNumberedFile.fileName)
let listFiles = ArrayList<Path>()
for (file in Directory.readFrom(firstNumberedFile.parent) where file.isRegular()) {
if (file.path.fileName.startsWith(zipFileNameWithoutExtension)) {
listFiles.add(file.path)
}
}
return unsafe { listFiles.getRawArray()[0..listFiles.size] }
}
public static func readSymbolicLink(file: Path): String {
return try {
SymbolicLink.readFrom(file).toString()
} catch (_) {
return ""
}
}
public static func getDefaultFileAttributes(isDirectory: Bool): Array<Byte> {
var permissions = Array<Byte>(4, repeat: 0)
if (isUnix || isMac) {
if (isDirectory) {
ArrayCopy(DEFAULT_POSIX_FOLDER_ATTRIBUTES, 0, permissions, 0, permissions.size)
} else {
ArrayCopy(DEFAULT_POSIX_FILE_ATTRIBUTES, 0, permissions, 0, permissions.size)
}
} else if (isWindows && isDirectory) {
permissions[0] = BitUtils.setBit(permissions[0], 4)
}
return permissions
}
private static func getExtensionZerosPrefix(index: Int64): String {
return if (index < 9) {
"00"
} else if (index < 99) {
"0"
} else {
""
}
}
// TODO 文件权限设置
private static func applyWindowsFileAttributes(file: Path, fileAttributes: Array<Byte>) {
file
if (fileAttributes[0] == 0) {
// No file attributes defined in the archive
return
}
}
// TODO 文件权限设置
private static func applyPosixFileAttributes(file: Path, fileAttributes: Array<Byte>): Unit {
file
if (fileAttributes[2] == 0 && fileAttributes[3] == 0) {
// No file attributes defined
return
}
}
private static func getWindowsFileAttributes(file: Path): Array<Byte> {
var fileAttributes = Array<Byte>(4, repeat:0)
try {
let dosFileAttributes = FileInfo(file)
var windowsAttribute: Byte = 0
windowsAttribute = setBitIfApplicable(dosFileAttributes.isReadOnly(), windowsAttribute, 0)
windowsAttribute = setBitIfApplicable(dosFileAttributes.isHidden(), windowsAttribute, 1)
windowsAttribute = setBitIfApplicable(false, windowsAttribute, 2)
windowsAttribute = setBitIfApplicable(dosFileAttributes.isDirectory(), windowsAttribute, 4)
windowsAttribute = setBitIfApplicable(true, windowsAttribute, 5)
fileAttributes[0] = windowsAttribute
} catch (_) {
// ignore
}
return fileAttributes
}
private static func assertFileExists(path: Path) {
if (!exists(path)) {
throw ZipException("File does not exist ${path}")
}
}
private static func assertSymbolicLinkTargetExists(path: Path) {
if (!exists(path)) {
throw ZipException("Symlink target '" + readSymbolicLink(path) + "' does not exist for link '${path}'")
}
}
private static func getPosixFileAttributes(file: Path): Array<Byte> {
var fileAttributes = Array<Byte>(4, repeat: 0)
try {
let fileInfo = FileInfo(file)
let isSymlink = fileInfo.isSymbolicLink()
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(fileInfo.isRegular(), fileAttributes[3], 7)
fileAttributes[3] = setBitIfApplicable(fileInfo.isDirectory(), fileAttributes[3], 6)
}
fileAttributes[3] = setBitIfApplicable(isSymlink, fileAttributes[3], 5)
fileAttributes[3] = setBitIfApplicable(fileInfo.canRead(), fileAttributes[3], 0)
fileAttributes[2] = setBitIfApplicable(fileInfo.canWrite(), fileAttributes[2], 7)
fileAttributes[2] = setBitIfApplicable(fileInfo.canExecute(), fileAttributes[2], 6)
fileAttributes[2] = setBitIfApplicable(fileInfo.canRead(), fileAttributes[2], 5)
fileAttributes[2] = setBitIfApplicable(fileInfo.canWrite(), fileAttributes[2], 4)
fileAttributes[2] = setBitIfApplicable(fileInfo.canExecute(), fileAttributes[2], 3)
fileAttributes[2] = setBitIfApplicable(fileInfo.canRead(), fileAttributes[2], 2)
fileAttributes[2] = setBitIfApplicable(fileInfo.canWrite(), fileAttributes[2], 1)
fileAttributes[2] = setBitIfApplicable(fileInfo.canExecute(), fileAttributes[2], 0)
} catch (e: Exception) {
// Ignore
}
return fileAttributes
}
private static func setBitIfApplicable(applicable: Bool, b: Byte, pos: Int64): Byte {
if (applicable) {
return BitUtils.setBit(b, pos)
}
return b
}
}