[frameworks/karchive] /: kzip: fix opening zip64 archives

Méven Car <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit b519abeff65e9e9109e824a52940adbfca495ccd by Méven Car, on behalf of George Florea Bănuș.
Committed on 18/07/2026 at 09:15.
Pushed by meven into branch 'master'.

kzip: fix opening zip64 archives

zip64 archives store (un)compressed size and local header offset in an extra field,
but only if the values don't fit in their 32-bit fields,
in which case the 32-bit fields are set to 0xFFFFFFFF

KArchive assumed all the 64-bit fields are set,
which leads to reading wrong data when they are not set

these changes fix the issue by first reading the 32-bit fields and passing them to
the parseExtraField function which then checks if they are set to 0xFFFFFFFF
and only then reads from the zip64 extra field

M  +4    -3    autotests/karchivetest.cpp
M  +49   -35   src/kzip.cpp

https://invent.kde.org/frameworks/karchive/-/commit/b519abeff65e9e9109e824a52940adbfca495ccd

diff --git a/autotests/karchivetest.cpp b/autotests/karchivetest.cpp
index ebc938e..47a13fc 100644
--- a/autotests/karchivetest.cpp
+++ b/autotests/karchivetest.cpp
@@ -1566,16 +1566,17 @@ void KArchiveTest::testZip64ExtraZip64SizeFirst()
 
     QBuffer zipBuffer(&zipData);
     KZip zip(&zipBuffer);
-    QEXPECT_FAIL("", "Zip64 extra at front incorrectly parsed", Abort);
     QVERIFY2(zip.open(QIODevice::ReadOnly), qPrintable(zip.errorString()));
-
     QCOMPARE(zip.directory()->entries().size(), 2);
-    QCOMPARE(zip.directory()->entries(), (QList{QStringLiteral("4200M.bin"), QStringLiteral("4.bin")}));
 
     auto entry = zip.directory()->file(QStringLiteral("4200M.bin"));
     QVERIFY(entry);
     QCOMPARE(entry->size(), 4404019208);
 
+    auto entry2 = zip.directory()->file(QStringLiteral("4.bin"));
+    QVERIFY(entry2);
+    QCOMPARE(entry2->size(), 4);
+
     auto readDev = std::unique_ptr<QIODevice>(entry->createDevice());
     auto head = readDev->read(8);
     QCOMPARE(head, QByteArrayLiteral("abcd\0\0\0\0"));
diff --git a/src/kzip.cpp b/src/kzip.cpp
index ffd9bf1..f059edf 100644
--- a/src/kzip.cpp
+++ b/src/kzip.cpp
@@ -247,12 +247,19 @@ static bool parseInfoZipUnixNew(const char *buffer, int size, bool islocal,
 
 /**
  * parses the extra field
+ *
+ * the 64-bit values for uncompressed size, compressed size and local header offset
+ * are retrieved only if the 32-bit values are 0xFFFFFFFF
+ *
  * @param buffer start of buffer where the extra field is to be found
  * @param size size of the extra field
  * @param pfi ParseFileInfo object which to write the results into
+ * @param uncompressedSize32 quint32 32-bit value for uncompressed size
+ * @param compressedSize32 quint32 32-bit value for compressed size
+ * @param localHeaderOffset32 quint32 32-bit value for local header offset
  * @return true if parsing was successful
  */
-static bool parseExtraField(const char *buffer, int size, ParseFileInfo &pfi)
+static bool parseExtraField(const char *buffer, int size, ParseFileInfo &pfi, quint32 uncompressedSize32, quint32 compressedSize32, quint32 localHeaderOffset32)
 {
     while (size >= 4) { // as long as a potential extra field can be read
         int magic = parseUi16(buffer);
@@ -267,17 +274,23 @@ static bool parseExtraField(const char *buffer, int size, ParseFileInfo &pfi)
         }
 
         switch (magic) {
-        case 0x0001: // ZIP64 extended file information
-            if (size >= 8) {
-                pfi.uncompressedSize = parseUi64(buffer);
+        case 0x0001: {
+            // ZIP64 extended file information
+            int offset = 0;
+            if (uncompressedSize32 == 0xFFFFFFFF && offset + 8 <= fieldsize) {
+                pfi.uncompressedSize = parseUi64(buffer + offset);
+                offset += 8;
             }
-            if (size >= 16) {
-                pfi.compressedSize = parseUi64(buffer + 8);
+            if (compressedSize32 == 0xFFFFFFFF && offset + 8 <= fieldsize) {
+                pfi.compressedSize = parseUi64(buffer + offset);
+                offset += 8;
             }
-            if (size >= 24) {
-                pfi.localheaderoffset = parseUi64(buffer + 16);
+            if (localHeaderOffset32 == 0xFFFFFFFF && offset + 8 <= fieldsize) {
+                pfi.localheaderoffset = parseUi64(buffer + offset);
+                offset += 8;
             }
             break;
+        }
         case 0x5455: // extended timestamp
             if (!parseExtTimestamp(buffer, fieldsize, true, pfi)) {
                 return false;
@@ -516,8 +529,8 @@ bool KZip::openArchive(QIODevice::OpenMode mode)
             int compression_mode = parseUi16(buffer + 4);
             uint mtime = transformFromMsDos(buffer + 6);
 
-            const qint64 compr_size = parseUi32(buffer + 14);
-            const qint64 uncomp_size = parseUi32(buffer + 18);
+            qint64 compr_size = parseUi32(buffer + 14);
+            qint64 uncomp_size = parseUi32(buffer + 18);
             const int namelen = parseUi16(buffer + 22);
             const int extralen = parseUi16(buffer + 24);
 
@@ -546,7 +559,7 @@ bool KZip::openArchive(QIODevice::OpenMode mode)
 
             // read and parse the beginning of the extra field,
             // skip rest of extra field in case it is too long
-            unsigned int extraFieldEnd = dev->pos() + extralen;
+            quint64 extraFieldEnd = dev->pos() + extralen;
             int handledextralen = qMin(extralen, (int)sizeof buffer);
 
             // if (handledextralen)
@@ -554,11 +567,18 @@ bool KZip::openArchive(QIODevice::OpenMode mode)
 
             n = dev->read(buffer, handledextralen);
             // no error msg necessary as we deliberately truncate the extra field
-            if (!parseExtraField(buffer, n, pfi)) {
+            if (!parseExtraField(buffer, n, pfi, uncomp_size, compr_size, 0)) {
                 setErrorString(tr("Invalid ZIP File. Broken ExtraField."));
                 return false;
             }
 
+            if (compr_size == 0xFFFFFFFF) {
+                compr_size = pfi.compressedSize;
+            }
+            if (uncomp_size == 0xFFFFFFFF) {
+                uncomp_size = pfi.uncompressedSize;
+            }
+
             // jump to end of extra field
             dev->seek(extraFieldEnd);
 
@@ -678,11 +698,27 @@ bool KZip::openArchive(QIODevice::OpenMode mode)
                 return false;
             }
 
+            // crc32 of the file
+            uint crc32 = parseUi32(buffer + 16);
+            // 32 bit uncompressed file size
+            quint32 ucsize32 = parseUi32(buffer + 24);
+            // 32 bit compressed file size
+            quint32 csize32 = parseUi32(buffer + 20);
+            // 32 bit offset of local header
+            quint32 localheaderoffset_32 = parseUi32(buffer + 42);
+
             ParseFileInfo extrafi;
             if (extralen) {
-                parseExtraField(varData.constData() + namelen, extralen, extrafi);
+                parseExtraField(varData.constData() + namelen, extralen, extrafi, ucsize32, csize32, localheaderoffset_32);
             }
 
+            // uncompressed file size
+            quint64 ucsize = (ucsize32 == 0xFFFFFFFF) ? extrafi.uncompressedSize : ucsize32;
+            // compressed file size
+            quint64 csize = (csize32 == 0xFFFFFFFF) ? extrafi.compressedSize : csize32;
+            // offset of local header
+            quint64 localheaderoffset = (localheaderoffset_32 == 0xFFFFFFFF) ? extrafi.localheaderoffset : localheaderoffset_32;
+
             QByteArray bufferName(varData.constData(), namelen);
 
             ParseFileInfo pfi = pfi_map.value(bufferName, ParseFileInfo());
@@ -690,30 +726,8 @@ bool KZip::openArchive(QIODevice::OpenMode mode)
             QString name(QFile::decodeName(bufferName));
 
             // qCDebug(KArchiveLog) << "name: " << name;
-
             // qCDebug(KArchiveLog) << "cmethod: " << cmethod;
             // qCDebug(KArchiveLog) << "extralen: " << extralen;
-
-            // crc32 of the file
-            uint crc32 = parseUi32(buffer + 16);
-
-            // uncompressed file size
-            quint64 ucsize = parseUi32(buffer + 24);
-            if (ucsize == 0xFFFFFFFF) {
-                ucsize = extrafi.uncompressedSize;
-            }
-            // compressed file size
-            quint64 csize = parseUi32(buffer + 20);
-            if (csize == 0xFFFFFFFF) {
-                csize = extrafi.compressedSize;
-            }
-
-            // offset of local header
-            quint64 localheaderoffset = parseUi32(buffer + 42);
-            if (localheaderoffset == 0xFFFFFFFF) {
-                localheaderoffset = extrafi.localheaderoffset;
-            }
-
             // qCDebug(KArchiveLog) << "localheader dataoffset: " << pfi.dataoffset;
 
             // offset, where the real data for uncompression starts
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.