diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/ZipImplementation.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/ZipImplementation.kt index df57296ca35..247e724a06f 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/ZipImplementation.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/ZipImplementation.kt @@ -28,6 +28,7 @@ class ZipEntryDescription( } private const val END_OF_CENTRAL_DIR_SIZE = 22 +private const val END_OF_CENTRAL_DIR_ZIP64_SIZE = 56 private const val LOCAL_FILE_HEADER_EXTRA_OFFSET = 28 private const val LOCAL_FILE_HEADER_SIZE = LOCAL_FILE_HEADER_EXTRA_OFFSET + 2 @@ -62,15 +63,7 @@ fun MappedByteBuffer.contentsToByteArray( fun MappedByteBuffer.parseCentralDirectory(): List { order(ByteOrder.LITTLE_ENDIAN) - var endOfCentralDirectoryOffset = capacity() - END_OF_CENTRAL_DIR_SIZE - while (endOfCentralDirectoryOffset >= 0) { - // header of "End of central directory" - if (getInt(endOfCentralDirectoryOffset) == 0x06054b50) break - endOfCentralDirectoryOffset-- - } - - val entriesNumber = getUnsignedShort(endOfCentralDirectoryOffset + 10) - val offsetOfCentralDirectory = getInt(endOfCentralDirectoryOffset + 16) + val (entriesNumber, offsetOfCentralDirectory) = parseCentralDirectoryRecordsNumberAndOffset() var currentOffset = offsetOfCentralDirectory @@ -129,4 +122,43 @@ fun MappedByteBuffer.parseCentralDirectory(): List { return result } +private fun MappedByteBuffer.parseCentralDirectoryRecordsNumberAndOffset(): Pair { + var endOfCentralDirectoryOffset = capacity() - END_OF_CENTRAL_DIR_SIZE + while (endOfCentralDirectoryOffset >= 0) { + // header of "End of central directory" (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) + if (getInt(endOfCentralDirectoryOffset) == 0x06054b50) break + endOfCentralDirectoryOffset-- + } + + val entriesNumber = getUnsignedShort(endOfCentralDirectoryOffset + 10) + val offsetOfCentralDirectory = getInt(endOfCentralDirectoryOffset + 16) + // Offset of start of central directory, relative to start of archive (or -1 for ZIP64) + // (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) + if (entriesNumber == 0xffff || offsetOfCentralDirectory == -1) return parseZip64CentralDirectoryRecordsNumberAndOffset() + + return Pair(entriesNumber, offsetOfCentralDirectory) +} + +private fun MappedByteBuffer.parseZip64CentralDirectoryRecordsNumberAndOffset(): Pair { + var endOfCentralDirectoryOffset = capacity() - END_OF_CENTRAL_DIR_ZIP64_SIZE + while (endOfCentralDirectoryOffset >= 0) { + // header of "End of central directory" (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) + if (getInt(endOfCentralDirectoryOffset) == 0x06064b50) break + endOfCentralDirectoryOffset-- + } + + val entriesNumber = getLong(endOfCentralDirectoryOffset + 32) + val offsetOfCentralDirectory = getLong(endOfCentralDirectoryOffset + 48) + + require(entriesNumber <= Int.MAX_VALUE) { + "Jar $entriesNumber entries number equal or more than ${Int.MAX_VALUE} is not supported by FastJarFS" + } + + require(offsetOfCentralDirectory <= Int.MAX_VALUE) { + "Jar $offsetOfCentralDirectory offset equal or more than ${Int.MAX_VALUE} is not supported by FastJarFS" + } + + return Pair(entriesNumber.toInt(), offsetOfCentralDirectory.toInt()) +} + private fun ByteBuffer.getUnsignedShort(offset: Int): Int = java.lang.Short.toUnsignedInt(getShort(offset)) diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/FastJarFSTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/FastJarFSTest.kt new file mode 100644 index 00000000000..d2beda8cda5 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/FastJarFSTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.jvm.compiler + +import junit.framework.TestCase +import org.jetbrains.kotlin.cli.jvm.compiler.jarfs.FastJarFileSystem +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.junit.Assert +import java.io.File +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + + +class FastJarFSTest : TestCase() { + + private var fs: FastJarFileSystem? = null + + override fun setUp() { + super.setUp() + fs = FastJarFileSystem.createIfUnmappingPossible() + } + + override fun tearDown() { + fs?.clearHandlersCache() + fs = null + super.tearDown() + } + + fun testZip64FormatIsSupported() { + val fs = fs ?: return + val tmpDir = KotlinTestUtils.tmpDirForTest(this) + val jarFile = File(tmpDir, "tmp.jar") + val out = ZipOutputStream(FileOutputStream(jarFile)) + + // Should be more than 65535 + val entriesNumber = 70000 + for (i in 0..entriesNumber) { + out.putNextEntry(ZipEntry("$i.txt")) + out.writer().apply { + append(i.toString()) + flush() + } + } + + out.close() + + val indicesToCheck = listOf( + 0, entriesNumber / 2, entriesNumber / 3, entriesNumber - 1 + ) + + for (i in indicesToCheck) { + val file = fs.findFileByPath(jarFile.absolutePath + "!/$i.txt") ?: error("Not found $i.txt") + Assert.assertEquals(String(file.contentsToByteArray()), i.toString()) + } + } +}