CLI implementation & linting (#2)
All checks were successful
Test Workflow / Lint and test library (push) Successful in 17m11s
Publish Workflow / Publish library (push) Successful in 8m49s

Implement a basic CLI interface & add linting to the project.

Reviewed-on: #2
This commit is contained in:
2023-12-30 06:36:49 +00:00
parent 36728fa1e9
commit 694b8d4d28
16 changed files with 563 additions and 229 deletions

View File

@@ -15,7 +15,7 @@ internal class CentralDirectoryFileHeader(
val disk: UShort,
val localHeaderOffset: UInt,
val fileName: String,
val extraFieldRecords: List<ExtraFieldRecord>
val extraFieldRecords: List<ExtraFieldRecord>,
) {
val size: Int
get() = SIZE + nameLength.toInt() + extraFieldLength.toInt() + commentLength.toInt()
@@ -32,65 +32,85 @@ internal class CentralDirectoryFileHeader(
* @return A `CentralDirectoryFileHeader`.
*/
@Throws(InvalidDataException::class)
fun fromByteArray(data: ByteArray, offset: Int): CentralDirectoryFileHeader {
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")
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
)
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)
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)
}
})
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

@@ -7,11 +7,12 @@ import java.nio.ByteOrder
* Represents a partial ZIP64 end of central directory locator.
*/
internal class EndOfCentralDirectoryLocator(
val endOfCentralDirectory64Offset: ULong
val endOfCentralDirectory64Offset: ULong,
) {
companion object {
const val SIGNATURE = 0x07064b50U
const val SIZE = 20
/**
* Create `EndOfCentralDirectoryLocator` from raw byte data.
* @throws InvalidDataException Provided `ByteArray` is not a supported EOCD locator.
@@ -20,13 +21,16 @@ internal class EndOfCentralDirectoryLocator(
* @return A `EndOfCentralDirectoryLocator`.
*/
@Throws(InvalidDataException::class)
fun fromByteArray(data: ByteArray, offset: Int): EndOfCentralDirectoryLocator {
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")
throw InvalidDataException("Invalid signature")
}
buf.position(offset + 8)
return EndOfCentralDirectoryLocator(buf.getLong().toULong())

View File

@@ -7,14 +7,14 @@ import java.nio.ByteOrder
* Represents a partial ZIP end of central directory record.
*/
internal class EndOfCentralDirectoryRecord(
val centralDirectoryOffset: UInt
val centralDirectoryOffset: UInt,
) {
fun eocd64Required(): Boolean =
centralDirectoryOffset == 0xffffffffU
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.
@@ -23,17 +23,20 @@ internal class EndOfCentralDirectoryRecord(
* @return A `EndOfCentralDirectoryRecord`.
*/
@Throws(InvalidDataException::class)
fun fromByteArray(data: ByteArray, offset: Int): EndOfCentralDirectoryRecord {
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")
throw InvalidDataException("Invalid signature")
}
buf.position(offset + 16)
return EndOfCentralDirectoryRecord(
centralDirectoryOffset = buf.getInt().toUInt()
centralDirectoryOffset = buf.getInt().toUInt(),
)
}
}

View File

@@ -7,11 +7,12 @@ import java.nio.ByteOrder
* Represents a partial ZIP64 end of central directory record.
*/
internal class EndOfCentralDirectoryRecord64(
val centralDirectoryOffset: ULong
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.
@@ -20,17 +21,20 @@ internal class EndOfCentralDirectoryRecord64(
* @return A `EndOfCentralDirectoryRecord64`.
*/
@Throws(InvalidDataException::class)
fun fromByteArray(data: ByteArray, offset: Int): EndOfCentralDirectoryRecord64 {
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")
throw InvalidDataException("Invalid signature")
}
buf.position(offset + 48)
return EndOfCentralDirectoryRecord64(
centralDirectoryOffset = buf.getLong().toULong()
centralDirectoryOffset = buf.getLong().toULong(),
)
}
}

View File

@@ -1,11 +0,0 @@
package ziputils
/**
* Represents an invalid raw byte data exception.
*/
class InvalidDataException(message: String): Exception(message)
/**
* Represents an invalid raw byte signature exception.
*/
class InvalidSignatureException(message: String): Exception(message)

View File

@@ -5,5 +5,5 @@ package ziputils
*/
internal open class ExtraFieldRecord(
val id: UShort,
val size: UShort
val size: UShort,
)

View File

@@ -0,0 +1,6 @@
package ziputils
/**
* Represents an invalid raw byte data exception.
*/
class InvalidDataException(message: String) : Exception(message)

View File

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