QMimeDatabase: don't stat() something that isn't a local file

We must check that the path is an actual file on the filesystem before
using native APIs. This regression was introduced by commit
047d8f36de.

[ChangeLog][QtCore][QMimeDatabase] Fixed a regression from 6.4.0 that
made certain QMimeDatabase functions that inspected file contents to fail
on Unix systems, if the file was not a native file (e.g., a Qt resource).

Fixes: QTBUG-110707
Pick-to: 6.4 6.5
Change-Id: I570832c9ac8b4e03bde8fffd173f7b4c6b164192
Reviewed-by: Igor Kushnir <igorkuo@gmail.com>
Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
Thiago Macieira 2023-01-31 11:25:54 -08:00
parent bfe42ebdeb
commit 40dd38813c
6 changed files with 87 additions and 24 deletions

View File

@ -444,31 +444,32 @@ QMimeType QMimeDatabasePrivate::mimeTypeForData(QIODevice *device)
}
QMimeType QMimeDatabasePrivate::mimeTypeForFile(const QString &fileName,
[[maybe_unused]] const QFileInfo *fileInfo,
const QFileInfo &fileInfo,
QMimeDatabase::MatchMode mode)
{
if (false) {
#ifdef Q_OS_UNIX
// Cannot access statBuf.st_mode from the filesystem engine, so we have to stat again.
// In addition we want to follow symlinks.
const QByteArray nativeFilePath = QFile::encodeName(fileName);
QT_STATBUF statBuffer;
if (QT_STAT(nativeFilePath.constData(), &statBuffer) == 0) {
if (S_ISDIR(statBuffer.st_mode))
return mimeTypeForName(directoryMimeType());
if (S_ISCHR(statBuffer.st_mode))
return mimeTypeForName(QStringLiteral("inode/chardevice"));
if (S_ISBLK(statBuffer.st_mode))
return mimeTypeForName(QStringLiteral("inode/blockdevice"));
if (S_ISFIFO(statBuffer.st_mode))
return mimeTypeForName(QStringLiteral("inode/fifo"));
if (S_ISSOCK(statBuffer.st_mode))
return mimeTypeForName(QStringLiteral("inode/socket"));
}
#else
const bool isDirectory = fileInfo ? fileInfo->isDir() : QFileInfo(fileName).isDir();
if (isDirectory)
return mimeTypeForName(directoryMimeType());
} else if (fileInfo.isNativePath()) {
// If this is a local file, we'll want to do a stat() ourselves so we can
// detect additional inode types. In addition we want to follow symlinks.
const QByteArray nativeFilePath = QFile::encodeName(fileName);
QT_STATBUF statBuffer;
if (QT_STAT(nativeFilePath.constData(), &statBuffer) == 0) {
if (S_ISDIR(statBuffer.st_mode))
return mimeTypeForName(directoryMimeType());
if (S_ISCHR(statBuffer.st_mode))
return mimeTypeForName(QStringLiteral("inode/chardevice"));
if (S_ISBLK(statBuffer.st_mode))
return mimeTypeForName(QStringLiteral("inode/blockdevice"));
if (S_ISFIFO(statBuffer.st_mode))
return mimeTypeForName(QStringLiteral("inode/fifo"));
if (S_ISSOCK(statBuffer.st_mode))
return mimeTypeForName(QStringLiteral("inode/socket"));
}
#endif
} else if (fileInfo.isDir()) {
return mimeTypeForName(directoryMimeType());
}
switch (mode) {
case QMimeDatabase::MatchDefault:
@ -615,7 +616,7 @@ QMimeType QMimeDatabase::mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mo
{
QMutexLocker locker(&d->mutex);
return d->mimeTypeForFile(fileInfo.filePath(), &fileInfo, mode);
return d->mimeTypeForFile(fileInfo.filePath(), fileInfo, mode);
}
/*!
@ -630,7 +631,8 @@ QMimeType QMimeDatabase::mimeTypeForFile(const QString &fileName, MatchMode mode
if (mode == MatchExtension) {
return d->mimeTypeForFileExtension(fileName);
} else {
return d->mimeTypeForFile(fileName, nullptr, mode);
QFileInfo fileInfo(fileName);
return d->mimeTypeForFile(fileName, fileInfo, mode);
}
}

View File

@ -60,7 +60,7 @@ public:
QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device);
QMimeType mimeTypeForFileExtension(const QString &fileName);
QMimeType mimeTypeForData(QIODevice *device);
QMimeType mimeTypeForFile(const QString &fileName, const QFileInfo *fileInfo, QMimeDatabase::MatchMode mode);
QMimeType mimeTypeForFile(const QString &fileName, const QFileInfo &fileInfo, QMimeDatabase::MatchMode mode);
QMimeType findByData(const QByteArray &data, int *priorityPtr);
QStringList mimeTypeForFileName(const QString &fileName);
QMimeGlobMatchResult findByFileName(const QString &fileName);

View File

@ -48,6 +48,14 @@ qt_internal_add_resource(tst_qmimedatabase-cache "testdata"
${testdata_resource_files}
)
qt_internal_add_resource(tst_qmimedatabase-cache "testfiles"
PREFIX
"/files"
FILES
"../test.txt"
"../test.qml"
)
# special case begin
set(corelib_source_dir ../../../../../../src/corelib)
include(${corelib_source_dir}/mimetypes/mimetypes_resources.cmake)

View File

@ -48,6 +48,14 @@ qt_internal_add_resource(tst_qmimedatabase-xml "testdata"
${testdata_resource_files}
)
qt_internal_add_resource(tst_qmimedatabase-xml "testfiles"
PREFIX
"/files"
FILES
"../test.txt"
"../test.qml"
)
# special case begin
set(corelib_source_dir ../../../../../../src/corelib)
include(${corelib_source_dir}/mimetypes/mimetypes_resources.cmake)

View File

@ -0,0 +1,6 @@
// Copyright (C) 2012 David Faure <faure@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 1.1
Item {
}

View File

@ -26,6 +26,8 @@
#include <QProcess>
#endif
using namespace Qt::StringLiterals;
static const char *const additionalMimeFiles[] = {
"yast2-metapackage-handler-mimetypes.xml",
"qml-again.xml",
@ -246,6 +248,7 @@ void tst_QMimeDatabase::mimeTypeForFileName_data()
// fdo bug 15436, needs shared-mime-info >= 0.40 (and this tests the globs2-parsing code).
QTest::newRow("glob that ends with *, also matches *.pdf. *.pdf has higher weight") << "README.pdf" << "application/pdf";
QTest::newRow("directory") << "/" << "inode/directory";
QTest::newRow("resource-directory") << ":/files/" << "inode/directory";
QTest::newRow("doesn't exist, no extension") << "IDontExist" << "application/octet-stream";
QTest::newRow("doesn't exist but has known extension") << "IDontExist.txt" << "text/plain";
QTest::newRow("empty") << "" << "application/octet-stream";
@ -498,6 +501,42 @@ void tst_QMimeDatabase::mimeTypeForFileWithContent()
QCOMPARE(mime.name(), QString::fromLatin1("application/smil+xml"));
}
// Test what happens with Qt resources (file engines in general)
{
QFile rccFile(":/files/test.txt");
mime = db.mimeTypeForFile(rccFile.fileName());
QCOMPARE(mime.name(), "text/plain"_L1);
QVERIFY(rccFile.open(QIODevice::ReadOnly));
mime = db.mimeTypeForData(&rccFile);
QCOMPARE(mime.name(), "text/x-qml"_L1);
QVERIFY(rccFile.isOpen());
mime = db.mimeTypeForFile(rccFile.fileName(), QMimeDatabase::MatchContent);
QCOMPARE(mime.name(), "text/x-qml"_L1);
}
// Directories
{
mime = db.mimeTypeForFile("/");
QCOMPARE(mime.name(), "inode/directory"_L1);
QString dirName = QDir::tempPath();
if (!dirName.endsWith(u'/'))
dirName += u'/';
mime = db.mimeTypeForFile(dirName);
QCOMPARE(mime.name(), "inode/directory"_L1);
while (dirName.endsWith(u'/'))
dirName.chop(1);
mime = db.mimeTypeForFile(dirName);
QCOMPARE(mime.name(), "inode/directory"_L1);
mime = db.mimeTypeForFile(":/files");
QCOMPARE(mime.name(), "inode/directory"_L1);
}
// Test what happens with an incorrect path
mime = db.mimeTypeForFile(QString::fromLatin1("file:///etc/passwd" /* incorrect code, use a path instead */));
QVERIFY(mime.isDefault());