Fix weird utf archive (#5186)

This commit is contained in:
Alexandru Ionut Tripon 2026-04-02 09:55:40 +00:00 committed by GitHub
commit a3c5f1f6f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 47 deletions

View file

@ -24,6 +24,7 @@
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QUrl> #include <QUrl>
#include <functional>
#include <memory> #include <memory>
#include <optional> #include <optional>
@ -36,25 +37,36 @@ QStringList ArchiveReader::getFiles()
bool ArchiveReader::collectFiles(bool onlyFiles) bool ArchiveReader::collectFiles(bool onlyFiles)
{ {
return parse([this, onlyFiles](File* f) { return parse([this, onlyFiles](File* f) {
if (!onlyFiles || f->isFile()) if (!onlyFiles || f->isFile()) {
m_fileNames << f->filename(); m_fileNames << f->filename();
}
return f->skip(); return f->skip();
}); });
} }
using getPathFunc = std::function<const char*(archive_entry*)>;
static QString decodeLibArchivePath(archive_entry* entry, const getPathFunc& getUtf8Path, const getPathFunc& getPath)
{
auto fileName = QString::fromUtf8(getUtf8Path(entry));
if (fileName.isEmpty()) {
fileName = QString::fromLocal8Bit(getPath(entry));
}
return fileName;
}
QString ArchiveReader::File::filename() QString ArchiveReader::File::filename()
{ {
return QString::fromUtf8(archive_entry_pathname_utf8(m_entry)); return decodeLibArchivePath(m_entry, archive_entry_pathname_utf8, archive_entry_pathname);
} }
QByteArray ArchiveReader::File::readAll(int* outStatus) QByteArray ArchiveReader::File::readAll(int* outStatus)
{ {
QByteArray data; QByteArray data;
const void* buff; const void* buff = nullptr;
size_t size; size_t size = 0;
la_int64_t offset; la_int64_t offset = 0;
int status; int status = 0;
while ((status = archive_read_data_block(m_archive.get(), &buff, &size, &offset)) == ARCHIVE_OK) { while ((status = archive_read_data_block(m_archive.get(), &buff, &size, &offset)) == ARCHIVE_OK) {
data.append(static_cast<const char*>(buff), static_cast<qsizetype>(size)); data.append(static_cast<const char*>(buff), static_cast<qsizetype>(size));
} }
@ -80,10 +92,10 @@ int ArchiveReader::File::readNextHeader()
return archive_read_next_header(m_archive.get(), &m_entry); return archive_read_next_header(m_archive.get(), &m_entry);
} }
auto ArchiveReader::goToFile(QString filename) -> std::unique_ptr<File> auto ArchiveReader::goToFile(const QString& filename) -> std::unique_ptr<File>
{ {
auto f = std::make_unique<File>(); auto f = std::make_unique<File>();
auto a = f->m_archive.get(); auto* a = f->m_archive.get();
archive_read_support_format_all(a); archive_read_support_format_all(a);
archive_read_support_filter_all(a); archive_read_support_filter_all(a);
auto fileName = m_archivePath.toStdWString(); auto fileName = m_archivePath.toStdWString();
@ -105,15 +117,16 @@ auto ArchiveReader::goToFile(QString filename) -> std::unique_ptr<File>
static int copy_data(struct archive* ar, struct archive* aw, bool notBlock = false) static int copy_data(struct archive* ar, struct archive* aw, bool notBlock = false)
{ {
int r; int r = 0;
const void* buff; const void* buff = nullptr;
size_t size; size_t size = 0;
la_int64_t offset; la_int64_t offset = 0;
for (;;) { for (;;) {
r = archive_read_data_block(ar, &buff, &size, &offset); r = archive_read_data_block(ar, &buff, &size, &offset);
if (r == ARCHIVE_EOF) if (r == ARCHIVE_EOF) {
return (ARCHIVE_OK); return ARCHIVE_OK;
}
if (r < ARCHIVE_OK) { if (r < ARCHIVE_OK) {
qCritical() << "Failed reading data block:" << archive_error_string(ar); qCritical() << "Failed reading data block:" << archive_error_string(ar);
return (r); return (r);
@ -130,39 +143,43 @@ static int copy_data(struct archive* ar, struct archive* aw, bool notBlock = fal
} }
} }
bool willEscapeRoot(const QDir& root, archive_entry* entry) static bool willEscapeRoot(const QDir& root, archive_entry* entry)
{ {
const char* entryPathC = archive_entry_pathname(entry); auto entryPath = decodeLibArchivePath(entry, archive_entry_pathname_utf8, archive_entry_pathname);
const char* linkTargetC = archive_entry_symlink(entry); auto linkTarget = decodeLibArchivePath(entry, archive_entry_symlink_utf8, archive_entry_symlink);
const char* hardlinkC = archive_entry_hardlink(entry); auto hardLink = decodeLibArchivePath(entry, archive_entry_hardlink_utf8, archive_entry_hardlink);
if (!entryPathC || (!linkTargetC && !hardlinkC)) if (entryPath.isEmpty() || (linkTarget.isEmpty() && hardLink.isEmpty())) {
return false; return false;
}
QString entryPath = QString::fromUtf8(entryPathC); bool isHardLink = false;
QString linkTarget = linkTargetC ? QString::fromUtf8(linkTargetC) : QString::fromUtf8(hardlinkC); if (isHardLink = linkTarget.isEmpty(); isHardLink) {
linkTarget = hardLink;
}
QString linkFullPath = root.filePath(entryPath); QString linkFullPath = root.filePath(entryPath);
auto rootDir = QUrl::fromLocalFile(root.absolutePath()); auto rootDir = QUrl::fromLocalFile(root.absolutePath());
if (!rootDir.isParentOf(QUrl::fromLocalFile(linkFullPath))) if (!rootDir.isParentOf(QUrl::fromLocalFile(linkFullPath))) {
return true; return true;
}
QDir linkDir = QFileInfo(linkFullPath).dir(); QDir linkDir = QFileInfo(linkFullPath).dir();
if (!QDir::isAbsolutePath(linkTarget)) { if (!QDir::isAbsolutePath(linkTarget)) {
linkTarget = (linkTargetC ? linkDir : root).filePath(linkTarget); linkTarget = (!isHardLink ? linkDir : root).filePath(linkTarget);
} }
return !rootDir.isParentOf(QUrl::fromLocalFile(QDir::cleanPath(linkTarget))); return !rootDir.isParentOf(QUrl::fromLocalFile(QDir::cleanPath(linkTarget)));
} }
bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, bool notBlock) bool ArchiveReader::File::writeFile(archive* out, const QString& targetFileName, bool notBlock)
{ {
return writeFile(out, targetFileName, {}, notBlock); return writeFile(out, targetFileName, {}, notBlock);
}; };
bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, std::optional<QDir> root, bool notBlock) bool ArchiveReader::File::writeFile(archive* out, const QString& targetFileName, std::optional<QDir> root, bool notBlock)
{ {
auto entry = m_entry; auto* entry = m_entry;
std::unique_ptr<archive_entry, decltype(&archive_entry_free)> entryClone(nullptr, &archive_entry_free); std::unique_ptr<archive_entry, decltype(&archive_entry_free)> entryClone(nullptr, &archive_entry_free);
if (!targetFileName.isEmpty()) { if (!targetFileName.isEmpty()) {
entryClone.reset(archive_entry_clone(m_entry)); entryClone.reset(archive_entry_clone(m_entry));
@ -175,25 +192,29 @@ bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, std::o
return false; return false;
} }
if (archive_write_header(out, entry) < ARCHIVE_OK) { if (archive_write_header(out, entry) < ARCHIVE_OK) {
qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out); qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out) << targetFileName;
return false; return false;
} else if (archive_entry_size(m_entry) > 0) { }
if (archive_entry_size(m_entry) > 0) {
auto r = copy_data(m_archive.get(), out, notBlock); auto r = copy_data(m_archive.get(), out, notBlock);
if (r < ARCHIVE_OK) if (r < ARCHIVE_OK) {
qCritical() << "Failed reading data block:" << archive_error_string(out); qCritical() << "Failed reading data block:" << archive_error_string(out);
if (r < ARCHIVE_WARN) }
if (r < ARCHIVE_WARN) {
return false; return false;
}
} }
auto r = archive_write_finish_entry(out); auto r = archive_write_finish_entry(out);
if (r < ARCHIVE_OK) if (r < ARCHIVE_OK) {
qCritical() << "Failed to finish writing entry:" << archive_error_string(out); qCritical() << "Failed to finish writing entry:" << archive_error_string(out);
}
return (r >= ARCHIVE_WARN); return (r >= ARCHIVE_WARN);
} }
bool ArchiveReader::parse(std::function<bool(File*, bool&)> doStuff) bool ArchiveReader::parse(const std::function<bool(File*, bool&)>& doStuff)
{ {
auto f = std::make_unique<File>(); auto f = std::make_unique<File>();
auto a = f->m_archive.get(); auto* a = f->m_archive.get();
archive_read_support_format_all(a); archive_read_support_format_all(a);
archive_read_support_filter_all(a); archive_read_support_filter_all(a);
auto fileName = m_archivePath.toStdWString(); auto fileName = m_archivePath.toStdWString();
@ -217,7 +238,7 @@ bool ArchiveReader::parse(std::function<bool(File*, bool&)> doStuff)
return true; return true;
} }
bool ArchiveReader::parse(std::function<bool(File*)> doStuff) bool ArchiveReader::parse(const std::function<bool(File*)>& doStuff)
{ {
return parse([doStuff](File* f, bool&) { return doStuff(f); }); return parse([doStuff](File* f, bool&) { return doStuff(f); });
} }
@ -241,26 +262,32 @@ QString ArchiveReader::getZipName()
bool ArchiveReader::exists(const QString& filePath) const bool ArchiveReader::exists(const QString& filePath) const
{ {
if (filePath == QLatin1String("/") || filePath.isEmpty()) if (filePath == QLatin1String("/") || filePath.isEmpty()) {
return true; return true;
}
// Normalize input path (remove trailing slash, if any) // Normalize input path (remove trailing slash, if any)
QString normalizedPath = QDir::cleanPath(filePath); QString normalizedPath = QDir::cleanPath(filePath);
if (normalizedPath.startsWith('/')) if (normalizedPath.startsWith('/')) {
normalizedPath.remove(0, 1); normalizedPath.remove(0, 1);
if (normalizedPath == QLatin1String(".")) }
if (normalizedPath == QLatin1String(".")) {
return true; return true;
if (normalizedPath == QLatin1String("..")) }
if (normalizedPath == QLatin1String("..")) {
return false; // root only return false; // root only
}
// Check for exact file match // Check for exact file match
if (m_fileNames.contains(normalizedPath, Qt::CaseInsensitive)) if (m_fileNames.contains(normalizedPath, Qt::CaseInsensitive)) {
return true; return true;
}
// Check for directory existence by seeing if any file starts with that path // Check for directory existence by seeing if any file starts with that path
QString dirPath = normalizedPath + QLatin1Char('/'); QString dirPath = normalizedPath + QLatin1Char('/');
for (const QString& f : m_fileNames) { for (const QString& f : m_fileNames) {
if (f.startsWith(dirPath, Qt::CaseInsensitive)) if (f.startsWith(dirPath, Qt::CaseInsensitive)) {
return true; return true;
}
} }
return false; return false;

View file

@ -23,6 +23,7 @@
#include <QStringList> #include <QStringList>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <utility>
struct archive; struct archive;
struct archive_entry; struct archive_entry;
@ -30,7 +31,7 @@ namespace MMCZip {
class ArchiveReader { class ArchiveReader {
public: public:
using ArchivePtr = std::unique_ptr<struct archive, int (*)(struct archive*)>; using ArchivePtr = std::unique_ptr<struct archive, int (*)(struct archive*)>;
ArchiveReader(QString fileName) : m_archivePath(fileName) {} explicit ArchiveReader(QString fileName) : m_archivePath(std::move(fileName)) {}
virtual ~ArchiveReader() = default; virtual ~ArchiveReader() = default;
QStringList getFiles(); QStringList getFiles();
@ -50,8 +51,8 @@ class ArchiveReader {
QByteArray readAll(int* outStatus = nullptr); QByteArray readAll(int* outStatus = nullptr);
bool skip(); bool skip();
bool writeFile(archive* out, QString targetFileName = "", bool notBlock = false); bool writeFile(archive* out, const QString& targetFileName = "", bool notBlock = false);
bool writeFile(archive* out, QString targetFileName, std::optional<QDir> root, bool notBlock = false); bool writeFile(archive* out, const QString& targetFileName, std::optional<QDir> root, bool notBlock = false);
private: private:
int readNextHeader(); int readNextHeader();
@ -62,14 +63,14 @@ class ArchiveReader {
archive_entry* m_entry; archive_entry* m_entry;
}; };
std::unique_ptr<File> goToFile(QString filename); std::unique_ptr<File> goToFile(const QString& filename);
bool parse(std::function<bool(File*)>); bool parse(const std::function<bool(File*)>&);
bool parse(std::function<bool(File*, bool&)>); bool parse(const std::function<bool(File*, bool&)>&);
private: private:
QString m_archivePath; QString m_archivePath;
size_t m_blockSize = 10240; size_t m_blockSize = 10240;
QStringList m_fileNames = {}; QStringList m_fileNames;
}; };
} // namespace MMCZip } // namespace MMCZip