summaryrefslogtreecommitdiff
path: root/package
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2024-02-01 20:55:40 +0600
committerMike Kaganski <mike.kaganski@collabora.com>2024-02-01 21:07:54 +0100
commit747463809e50c132557a95dcee6709a1fa82d760 (patch)
treec06b1a0a5f68ef888a2c2558ad6d47f86d32201d /package
parentc3d00f4ca0ec5a53d694ab9924fd79d0e536aec4 (diff)
tdf#154587: allow directory entries in ZIP packages
The problem in the bugdoc was the directory entries. These entries are valid in ZIP packages (even if not common); they may be useful to e.g. define per-directory permissions (ACLs). In normal mode, ZipFile reads central directory; there we can read if the entry has FAT file attributes; and then, if the entry is a directory. Then it is OK to skip it. In repair mode, central directory is not used, local file headers don't contain a "directory" flag. A workaround is used, checking if there are entries that represent directories of other entries. Also this change fixes some places that didn't pass the recovery flag correctly. Change-Id: I324671841a2c4d0f279b03801d95c8f2eeb99b46 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162888 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Diffstat (limited to 'package')
-rw-r--r--package/source/zipapi/ZipFile.cxx42
1 files changed, 40 insertions, 2 deletions
diff --git a/package/source/zipapi/ZipFile.cxx b/package/source/zipapi/ZipFile.cxx
index 474b73ff53db..71fd66f08196 100644
--- a/package/source/zipapi/ZipFile.cxx
+++ b/package/source/zipapi/ZipFile.cxx
@@ -38,6 +38,7 @@
#include <rtl/digest.h>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <algorithm>
@@ -1073,7 +1074,7 @@ sal_Int32 ZipFile::readCEN()
if ( nTestSig != CENSIG )
throw ZipException("Invalid CEN header (bad signature)" );
- aMemGrabber.skipBytes ( 2 );
+ sal_uInt16 versionMadeBy = aMemGrabber.ReadUInt16();
aEntry.nVersion = aMemGrabber.ReadInt16();
aEntry.nFlag = aMemGrabber.ReadInt16();
@@ -1093,7 +1094,8 @@ sal_Int32 ZipFile::readCEN()
aEntry.nPathLen = aMemGrabber.ReadInt16();
aEntry.nExtraLen = aMemGrabber.ReadInt16();
nCommentLen = aMemGrabber.ReadInt16();
- aMemGrabber.skipBytes ( 8 );
+ aMemGrabber.skipBytes ( 4 );
+ sal_uInt32 externalFileAttributes = aMemGrabber.ReadUInt32();
sal_uInt64 nOffset = aMemGrabber.ReadUInt32();
if ( aEntry.nPathLen < 0 )
@@ -1132,6 +1134,15 @@ sal_Int32 ZipFile::readCEN()
throw ZipException("Integer-overflow");
aMemGrabber.skipBytes(nCommentLen);
+
+ // Is this a FAT-compatible empty entry?
+ if (aEntry.nSize == 0 && (versionMadeBy & 0xff00) == 0)
+ {
+ constexpr sal_uInt32 FILE_ATTRIBUTE_DIRECTORY = 16;
+ if (externalFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ continue; // This is a directory entry, not a stream - skip it
+ }
+
aEntries[aEntry.sPath] = aEntry;
}
@@ -1253,6 +1264,7 @@ void ZipFile::recover()
RTL_TEXTENCODING_UTF8 );
aEntry.nPathLen = static_cast< sal_Int16 >(aFileName.getLength());
}
+ aEntry.sPath = aEntry.sPath.replace('\\', '/');
// read 64bit header
if (aEntry.nExtraLen > 0)
@@ -1294,7 +1306,33 @@ void ZipFile::recover()
aEntry.nSize = 0;
}
+ // Do not add this entry, if it is empty and is a directory of
+ // an already existing entry
+ if (aEntry.nSize == 0 && aEntry.nCompressedSize == 0
+ && std::find_if(
+ aEntries.begin(), aEntries.end(),
+ [path = OUString(aEntry.sPath + "/")](const auto& r)
+ { return r.first.startsWith(path); })
+ != aEntries.end())
+ continue;
+
aEntries.emplace( aEntry.sPath, aEntry );
+
+ // Drop any "directory" entry corresponding to this one's path;
+ // since we don't use central directory, we don't see external
+ // file attributes, so sanitize here
+ sal_Int32 i = 0;
+ for (OUString subdir = aEntry.sPath.getToken(0, '/', i); i >= 0;
+ subdir += OUString::Concat("/")
+ + o3tl::getToken(aEntry.sPath, 0, '/', i))
+ {
+ if (auto it = aEntries.find(subdir); it != aEntries.end())
+ {
+ // if not empty, let it fail later in ZipPackage::getZipFileContents
+ if (it->second.nSize == 0 && it->second.nCompressedSize == 0)
+ aEntries.erase(it);
+ }
+ }
}
}
}