Initial implementations

This commit is contained in:
2023-12-29 18:43:54 +06:00
commit 6ebf969d35
24 changed files with 991 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
package ziputils
import java.nio.ByteBuffer
import java.nio.ByteOrder
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.
*/
@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 InvalidSignatureException("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
}
}
}

View File

@@ -0,0 +1,25 @@
package ziputils
import java.nio.ByteBuffer
import java.nio.ByteOrder
internal class EndOfCentralDirectoryLocator(
val endOfCentralDirectory64Offset: ULong
) {
companion object {
const val SIGNATURE = 0x07064b50U
const val SIZE = 20
@Throws(InvalidDataException::class)
fun fromByteArray(data: ByteArray, offset: Int): EndOfCentralDirectoryLocator {
if (data.size - offset < SIZE) {
throw InvalidDataException("EOCD64 locator must be at least 20 bytes")
}
val buf = ByteBuffer.wrap(data, offset, SIZE).order(ByteOrder.LITTLE_ENDIAN)
if (buf.getInt().toUInt() != SIGNATURE) {
throw InvalidSignatureException("Invalid signature")
}
buf.position(offset + 8)
return EndOfCentralDirectoryLocator(buf.getLong().toULong())
}
}
}

View File

@@ -0,0 +1,38 @@
package ziputils
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* Partial End of Central Directory record class.
* Only supports data required by the backup tool.
*/
internal class EndOfCentralDirectoryRecord(
val centralDirectoryOffset: UInt
) {
fun eocd64Required(): Boolean =
centralDirectoryOffset == 0xffffffffU
companion object {
const val SIGNATURE = 0x06054b50U
const val SIZE = 22
/**
* Create EndOfCentralDirectoryRecord from raw byte data.
* @throws InvalidDataException provided ByteArray is not a supported EOCD64.
*/
@Throws(InvalidDataException::class)
fun fromByteArray(data: ByteArray, offset: Int): EndOfCentralDirectoryRecord {
if (data.size - offset < SIZE) {
throw InvalidDataException("EOCD must be at least 22 bytes")
}
val buf = ByteBuffer.wrap(data, offset, SIZE).order(ByteOrder.LITTLE_ENDIAN)
if (buf.getInt().toUInt() != SIGNATURE) {
throw InvalidSignatureException("Invalid signature")
}
buf.position(offset + 16)
return EndOfCentralDirectoryRecord(
centralDirectoryOffset = buf.getInt().toUInt()
)
}
}
}

View File

@@ -0,0 +1,35 @@
package ziputils
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* Partial End of Central Directory record (ZIP64) class.
* Only supports data required by the backup tool.
*/
internal class EndOfCentralDirectoryRecord64(
val centralDirectoryOffset: ULong
) {
companion object {
const val SIGNATURE = 0x06064b50U
const val SIZE = 56
/**
* Create EndOfCentralDirectoryRecord64 from raw byte data.
* @throws InvalidDataException provided ByteArray is not a supported EOCD.
*/
@Throws(InvalidDataException::class)
fun fromByteArray(data: ByteArray, offset: Int): EndOfCentralDirectoryRecord64 {
if (data.size - offset < SIZE) {
throw InvalidDataException("EOCD64 must be at least 56 bytes")
}
val buf = ByteBuffer.wrap(data, offset, SIZE).order(ByteOrder.LITTLE_ENDIAN)
if (buf.getInt().toUInt() != SIGNATURE) {
throw InvalidSignatureException("Invalid signature")
}
buf.position(offset + 48)
return EndOfCentralDirectoryRecord64(
centralDirectoryOffset = buf.getLong().toULong()
)
}
}
}

View File

@@ -0,0 +1,4 @@
package ziputils
class InvalidDataException(message: String): Exception(message)
class InvalidSignatureException(message: String): Exception(message)

View File

@@ -0,0 +1,6 @@
package ziputils
internal open class ExtraFieldRecord(
val id: UShort,
val size: UShort
)

View File

@@ -0,0 +1,6 @@
# ZipUtils
These are **internal** utility classes for reading zip file metadata. They only implement what is required for the
backup tool, and no more.
Specifically, we are looking at the [ZIP v6.3.9 specification](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.9.TXT).

View File

@@ -0,0 +1,13 @@
package ziputils
internal class Zip64ExtraFieldRecord(
size: UShort,
val uncompressedSize: ULong?,
val compressedSize: ULong?,
val localHeaderOffset: ULong?,
val disk: UInt?
): ExtraFieldRecord(ID, size) {
companion object {
const val ID: UShort = 0x0001U
}
}