119 lines
4.7 KiB
Kotlin
119 lines
4.7 KiB
Kotlin
package ziputils
|
|
|
|
import java.nio.ByteBuffer
|
|
import java.nio.ByteOrder
|
|
|
|
/**
|
|
* Represents a partial ZIP central directory file header.
|
|
*/
|
|
internal class CentralDirectoryFileHeader(
|
|
val compressedSize: UInt,
|
|
val uncompressedSize: UInt,
|
|
val nameLength: UShort,
|
|
val extraFieldLength: UShort,
|
|
val commentLength: UShort,
|
|
val disk: UShort,
|
|
val localHeaderOffset: UInt,
|
|
val fileName: String,
|
|
val extraFieldRecords: List<ExtraFieldRecord>,
|
|
) {
|
|
val size: Int
|
|
get() = SIZE + nameLength.toInt() + extraFieldLength.toInt() + commentLength.toInt()
|
|
|
|
companion object {
|
|
const val SIGNATURE = 0x02014b50U
|
|
const val SIZE = 46
|
|
|
|
/**
|
|
* Create `CentralDirectoryFileHeader` from raw byte data.
|
|
* @throws InvalidDataException provided `ByteArray` is not a supported CEN.
|
|
* @param data Raw byte data.
|
|
* @param offset Skip first <offset> bytes in data array.
|
|
* @return A `CentralDirectoryFileHeader`.
|
|
*/
|
|
@Throws(InvalidDataException::class)
|
|
fun fromByteArray(
|
|
data: ByteArray,
|
|
offset: Int,
|
|
): CentralDirectoryFileHeader {
|
|
if (data.size - offset < SIZE) {
|
|
throw InvalidDataException("CEN must be at least 46 bytes")
|
|
}
|
|
val buf = ByteBuffer.wrap(data, offset, 46).order(ByteOrder.LITTLE_ENDIAN)
|
|
if (buf.getInt().toUInt() != SIGNATURE) {
|
|
throw InvalidDataException("Invalid signature")
|
|
}
|
|
|
|
val extraFieldRecords = mutableListOf<ExtraFieldRecord>()
|
|
val nameLength = buf.getShort(offset + 28).toUShort()
|
|
buf.position(offset + 20)
|
|
val cen =
|
|
CentralDirectoryFileHeader(
|
|
compressedSize = buf.getInt().toUInt(),
|
|
uncompressedSize = buf.getInt().toUInt(),
|
|
nameLength =
|
|
nameLength
|
|
.also { buf.position(offset + 30) },
|
|
extraFieldLength = buf.getShort().toUShort(),
|
|
commentLength = buf.getShort().toUShort(),
|
|
disk =
|
|
buf.getShort().toUShort()
|
|
.also { buf.position(offset + 42) },
|
|
localHeaderOffset = buf.getInt().toUInt(),
|
|
fileName = String(data.sliceArray(offset + SIZE..<offset + SIZE + nameLength.toInt())),
|
|
extraFieldRecords = extraFieldRecords,
|
|
)
|
|
if (data.size - offset < cen.size) {
|
|
throw InvalidDataException("CEN is too short")
|
|
}
|
|
|
|
// Parse extra field records
|
|
val extraFieldsBuf =
|
|
ByteBuffer.wrap(
|
|
data,
|
|
offset + SIZE + cen.nameLength.toInt(),
|
|
cen.extraFieldLength.toInt(),
|
|
).order(ByteOrder.LITTLE_ENDIAN)
|
|
while (extraFieldsBuf.remaining() > 0) {
|
|
val id = extraFieldsBuf.getShort().toUShort()
|
|
val size = extraFieldsBuf.getShort().toUShort()
|
|
extraFieldRecords.add(
|
|
when (id) {
|
|
Zip64ExtraFieldRecord.ID -> {
|
|
Zip64ExtraFieldRecord(
|
|
size,
|
|
if (cen.uncompressedSize == 0xffffffffU) {
|
|
extraFieldsBuf.getLong().toULong()
|
|
} else {
|
|
null
|
|
},
|
|
if (cen.compressedSize == 0xffffffffU) {
|
|
extraFieldsBuf.getLong().toULong()
|
|
} else {
|
|
null
|
|
},
|
|
if (cen.localHeaderOffset == 0xffffffffU) {
|
|
extraFieldsBuf.getLong().toULong()
|
|
} else {
|
|
null
|
|
},
|
|
if (cen.disk == 0xffffU.toUShort()) {
|
|
extraFieldsBuf.getInt().toUInt()
|
|
} else {
|
|
null
|
|
},
|
|
)
|
|
}
|
|
|
|
else -> {
|
|
extraFieldsBuf.position(extraFieldsBuf.position() + size.toInt())
|
|
ExtraFieldRecord(id, size)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
return cen
|
|
}
|
|
}
|
|
} |