summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qresource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qresource.cpp')
-rw-r--r--src/corelib/io/qresource.cpp304
1 files changed, 156 insertions, 148 deletions
diff --git a/src/corelib/io/qresource.cpp b/src/corelib/io/qresource.cpp
index 8f0440ef2e..df57d12efc 100644
--- a/src/corelib/io/qresource.cpp
+++ b/src/corelib/io/qresource.cpp
@@ -1,42 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 The Qt Company Ltd.
-** Copyright (C) 2020 Intel Corporation.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtCore module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// Copyright (C) 2020 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qresource.h"
#include "qresource_p.h"
@@ -69,15 +33,20 @@
# include <zstd.h>
#endif
-#if defined(Q_OS_UNIX) && !defined(Q_OS_NACL) && !defined(Q_OS_INTEGRITY)
+#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
# define QT_USE_MMAP
# include <sys/mman.h>
#endif
+#ifdef Q_OS_WIN
+# include <qt_windows.h>
+#endif
//#define DEBUG_RESOURCE_MATCH
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
// Symbols used by code generated by RCC.
// They cause compilation errors if the RCC content couldn't
// be interpreted by this QtCore version.
@@ -99,6 +68,7 @@ RCC_FEATURE_SYMBOL(Zstd)
#undef RCC_FEATURE_SYMBOL
+namespace {
class QStringSplitter
{
public:
@@ -116,7 +86,7 @@ public:
inline QStringView next()
{
- int start = m_pos;
+ const qsizetype start = m_pos;
while (m_pos < m_len && m_data[m_pos] != m_splitChar)
++m_pos;
return QStringView(m_data + start, m_pos - start);
@@ -125,7 +95,7 @@ public:
const QChar *m_data;
qsizetype m_len;
qsizetype m_pos = 0;
- QChar m_splitChar = QLatin1Char('/');
+ QChar m_splitChar = u'/';
};
// resource glue
@@ -164,7 +134,7 @@ public:
return QResource::NoCompression;
}
const uchar *data(int node, qint64 *size) const;
- quint64 lastModified(int node) const;
+ qint64 lastModified(int node) const;
QStringList children(int node) const;
virtual QString mappingRoot() const { return QString(); }
bool mappingRootSubdir(const QString &path, QString *match = nullptr) const;
@@ -189,20 +159,22 @@ static QString cleanPath(const QString &_path)
QString path = QDir::cleanPath(_path);
// QDir::cleanPath does not remove two trailing slashes under _Windows_
// due to support for UNC paths. Remove those manually.
- if (path.startsWith(QLatin1String("//")))
+ if (path.startsWith("//"_L1))
path.remove(0, 1);
return path;
}
+} // unnamed namespace
Q_DECLARE_TYPEINFO(QResourceRoot, Q_RELOCATABLE_TYPE);
typedef QList<QResourceRoot*> ResourceList;
+namespace {
struct QResourceGlobalData
{
QRecursiveMutex resourceMutex;
ResourceList resourceList;
- QStringList resourceSearchPaths;
};
+}
Q_GLOBAL_STATIC(QResourceGlobalData, resourceGlobalData)
static inline QRecursiveMutex &resourceMutex()
@@ -211,9 +183,6 @@ static inline QRecursiveMutex &resourceMutex()
static inline ResourceList *resourceList()
{ return &resourceGlobalData->resourceList; }
-static inline QStringList *resourceSearchPaths()
-{ return &resourceGlobalData->resourceSearchPaths; }
-
/*!
\class QResource
\inmodule QtCore
@@ -271,6 +240,19 @@ static inline QStringList *resourceSearchPaths()
itself will be unmapped from memory when the last QResource that points
to it is destroyed.
+ \section2 Corruption and Security
+
+ The QResource class performs some checks on the file passed to determine
+ whether it is supported by the current version of Qt. Those tests are only
+ to check the file header does not request features (such as Zstandard
+ decompression) that have not been compiled in or that the file is not of a
+ future version of Qt. They do not confirm the validity of the entire file.
+
+ QResource should not be used on files whose provenance cannot be trusted.
+ Applications should be designed to attempt to load only resource files
+ whose provenance is at least as trustworthy as that of the application
+ itself or its plugins.
+
\sa {The Qt Resource System}, QFile, QDir, QFileInfo
*/
@@ -307,11 +289,11 @@ public:
QLocale locale;
QString fileName, absoluteFilePath;
QList<QResourceRoot *> related;
- mutable qint64 size;
- mutable quint64 lastModified;
- mutable const uchar *data;
+ qint64 size;
+ qint64 lastModified;
+ const uchar *data;
mutable QStringList children;
- mutable quint8 compressionAlgo;
+ quint8 compressionAlgo;
bool container;
/* 2 or 6 padding bytes */
@@ -381,29 +363,23 @@ void QResourcePrivate::ensureInitialized() const
if (!related.isEmpty())
return;
QResourcePrivate *that = const_cast<QResourcePrivate *>(this);
- if (fileName == QLatin1String(":"))
- that->fileName += QLatin1Char('/');
+ if (fileName == ":"_L1)
+ that->fileName += u'/';
that->absoluteFilePath = fileName;
- if (!that->absoluteFilePath.startsWith(QLatin1Char(':')))
- that->absoluteFilePath.prepend(QLatin1Char(':'));
+ if (!that->absoluteFilePath.startsWith(u':'))
+ that->absoluteFilePath.prepend(u':');
QStringView path(fileName);
- if (path.startsWith(QLatin1Char(':')))
+ if (path.startsWith(u':'))
path = path.mid(1);
- if (path.startsWith(QLatin1Char('/'))) {
+ if (path.startsWith(u'/')) {
that->load(path.toString());
} else {
- const auto locker = qt_scoped_lock(resourceMutex());
- QStringList searchPaths = *resourceSearchPaths();
- searchPaths << QLatin1String("");
- for (int i = 0; i < searchPaths.size(); ++i) {
- const QString searchPath(searchPaths.at(i) + QLatin1Char('/') + path);
- if (that->load(searchPath)) {
- that->absoluteFilePath = QLatin1Char(':') + searchPath;
- break;
- }
- }
+ // Should we search QDir::searchPath() before falling back to root ?
+ const QString searchPath(u'/' + path);
+ if (that->load(searchPath))
+ that->absoluteFilePath = u':' + searchPath;
}
}
@@ -414,7 +390,7 @@ void QResourcePrivate::ensureChildren() const
return;
QString path = absoluteFilePath, k;
- if (path.startsWith(QLatin1Char(':')))
+ if (path.startsWith(u':'))
path = path.mid(1);
QDuplicateTracker<QString> kids(related.size());
QString cleaned = cleanPath(path);
@@ -797,7 +773,7 @@ inline QString QResourceRoot::name(int node) const
ret.resize(name_length);
QChar *strData = ret.data();
- qFromBigEndian<ushort>(names + name_offset, name_length, strData);
+ qFromBigEndian<char16_t>(names + name_offset, name_length, strData);
return ret;
}
@@ -808,14 +784,14 @@ int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
QString root = mappingRoot();
if (!root.isEmpty()) {
if (root == path) {
- path = QLatin1Char('/');
+ path = u'/';
} else {
- if (!root.endsWith(QLatin1Char('/')))
- root += QLatin1Char('/');
+ if (!root.endsWith(u'/'))
+ root += u'/';
if (path.size() >= root.size() && path.startsWith(root))
- path = path.mid(root.length() - 1);
+ path = path.mid(root.size() - 1);
if (path.isEmpty())
- path = QLatin1Char('/');
+ path = u'/';
}
}
}
@@ -823,7 +799,7 @@ int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
qDebug() << "!!!!" << "START" << path << locale.territory() << locale.language();
#endif
- if (path == QLatin1String("/"))
+ if (path == "/"_L1)
return 0;
// the root node is always first
@@ -959,14 +935,14 @@ const uchar *QResourceRoot::data(int node, qint64 *size) const
return nullptr;
}
-quint64 QResourceRoot::lastModified(int node) const
+qint64 QResourceRoot::lastModified(int node) const
{
if (node == -1 || version < 0x02)
return 0;
const int offset = findOffset(node) + 14;
- return qFromBigEndian<quint64>(tree + offset);
+ return qFromBigEndian<qint64>(tree + offset);
}
QStringList QResourceRoot::children(int node) const
@@ -1062,8 +1038,8 @@ Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tre
return false;
}
+namespace {
// run time resource creation
-
class QDynamicBufferResourceRoot : public QResourceRoot
{
QString root;
@@ -1136,6 +1112,11 @@ public:
class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot
{
+public:
+ static uchar *map_sys(QFile &file, qint64 base, qsizetype size);
+ static void unmap_sys(void *base, qsizetype size);
+
+private:
QString fileName;
// for mmap'ed files, this is what needs to be unmapped.
uchar *unmapPointer;
@@ -1146,22 +1127,17 @@ public:
: QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0)
{ }
~QDynamicFileResourceRoot() {
-#if defined(QT_USE_MMAP)
- if (unmapPointer) {
- munmap(reinterpret_cast<char *>(unmapPointer), unmapLength);
- unmapPointer = nullptr;
- unmapLength = 0;
- } else
-#endif
- {
+ if (unmapPointer)
+ unmap_sys(unmapPointer, unmapLength);
+ else
delete[] mappingBuffer();
- }
}
QString mappingFile() const { return fileName; }
ResourceRootType type() const override { return Resource_File; }
bool registerSelf(const QString &f);
};
+} // unnamed namespace
#ifndef MAP_FILE
# define MAP_FILE 0
@@ -1170,49 +1146,69 @@ public:
# define MAP_FAILED reinterpret_cast<void *>(-1)
#endif
-bool QDynamicFileResourceRoot::registerSelf(const QString &f)
+void QDynamicFileResourceRoot::unmap_sys(void *base, qsizetype size)
{
- bool fromMM = false;
- uchar *data = nullptr;
- qsizetype data_len = 0;
+#if defined(QT_USE_MMAP)
+ munmap(base, size);
+#elif defined(Q_OS_WIN)
+ Q_UNUSED(size)
+ UnmapViewOfFile(reinterpret_cast<void *>(base));
+#endif
+}
+// Note: caller must ensure \a offset and \a size are acceptable to the OS.
+uchar *QDynamicFileResourceRoot::map_sys(QFile &file, qint64 offset, qsizetype size)
+{
+ Q_ASSERT(file.isOpen());
+ void *ptr = nullptr;
+ if (size < 0)
+ size = qMin(file.size() - offset, (std::numeric_limits<qsizetype>::max)());
+
+ // We don't use QFile::map() here because we want to dispose of the QFile object
#if defined(QT_USE_MMAP)
- int fd = QT_OPEN(QFile::encodeName(f), O_RDONLY);
- if (fd >= 0) {
- QT_STATBUF st;
- if (!QT_FSTAT(fd, &st) && st.st_size <= std::numeric_limits<qsizetype>::max()) {
- int protection = PROT_READ; // read-only memory
- int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
- void *ptr = QT_MMAP(nullptr, st.st_size, // any address, whole file
- protection, flags,
- fd, 0); // from offset 0 of fd
- if (ptr != MAP_FAILED) {
- data = static_cast<uchar *>(ptr);
- data_len = st.st_size;
- fromMM = true;
- }
+ int fd = file.handle();
+ int protection = PROT_READ; // read-only memory
+ int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
+ ptr = QT_MMAP(nullptr, size, protection, flags, fd, offset);
+ if (ptr == MAP_FAILED)
+ ptr = nullptr;
+#elif defined(Q_OS_WIN)
+ int fd = file.handle();
+ HANDLE fileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+ if (fileHandle != INVALID_HANDLE_VALUE) {
+ HANDLE mapHandle = CreateFileMapping(fileHandle, 0, PAGE_WRITECOPY, 0, 0, 0);
+ if (mapHandle) {
+ ptr = MapViewOfFile(mapHandle, FILE_MAP_COPY, DWORD(offset >> 32), DWORD(offset), size);
+ CloseHandle(mapHandle);
}
- QT_CLOSE(fd);
}
#endif // QT_USE_MMAP
- if (!data) {
- QFile file(f);
+ return static_cast<uchar *>(ptr);
+}
+
+bool QDynamicFileResourceRoot::registerSelf(const QString &f)
+{
+ QFile file(f);
+ if (!file.open(QIODevice::ReadOnly))
+ return false;
+
+ qint64 data_len = file.size();
+ if (data_len > std::numeric_limits<qsizetype>::max())
+ return false;
+
+ uchar *data = map_sys(file, 0, data_len);
+ bool fromMM = !!data;
+
+ if (!fromMM) {
bool ok = false;
- if (file.open(QIODevice::ReadOnly)) {
- qint64 fsize = file.size();
- if (fsize <= std::numeric_limits<qsizetype>::max()) {
- data_len = file.size();
- data = new uchar[data_len];
- ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len));
- }
- }
+ data = new uchar[data_len];
+ ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len));
if (!ok) {
delete[] data;
data = nullptr;
data_len = 0;
return false;
}
- fromMM = false;
}
if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len)) {
if (fromMM) {
@@ -1228,7 +1224,7 @@ bool QDynamicFileResourceRoot::registerSelf(const QString &f)
static QString qt_resource_fixResourceRoot(QString r)
{
if (!r.isEmpty()) {
- if (r.startsWith(QLatin1Char(':')))
+ if (r.startsWith(u':'))
r = r.mid(1);
if (!r.isEmpty())
r = QDir::cleanPath(r);
@@ -1249,7 +1245,7 @@ static QString qt_resource_fixResourceRoot(QString r)
bool QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
- if (!r.isEmpty() && r[0] != QLatin1Char('/')) {
+ if (!r.isEmpty() && r[0] != u'/') {
qWarning("QDir::registerResource: Registering a resource [%ls] must be rooted in an "
"absolute path (start with /) [%ls]",
qUtf16Printable(rccFilename), qUtf16Printable(resourceRoot));
@@ -1318,7 +1314,7 @@ bool QResource::unregisterResource(const QString &rccFilename, const QString &re
bool QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
- if (!r.isEmpty() && r[0] != QLatin1Char('/')) {
+ if (!r.isEmpty() && r[0] != u'/') {
qWarning("QDir::registerResource: Registering a resource [%p] must be rooted in an "
"absolute path (start with /) [%ls]",
rccData, qUtf16Printable(resourceRoot));
@@ -1380,6 +1376,7 @@ private:
uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
bool unmap(uchar *ptr);
void uncompress() const;
+ void mapUncompressed();
qint64 offset;
QResource resource;
mutable QByteArray uncompressed;
@@ -1409,8 +1406,11 @@ void QResourceFileEngine::setFileName(const QString &file)
d->resource.setFileName(file);
}
-bool QResourceFileEngine::open(QIODevice::OpenMode flags)
+bool QResourceFileEngine::open(QIODevice::OpenMode flags,
+ std::optional<QFile::Permissions> permissions)
{
+ Q_UNUSED(permissions);
+
Q_D(QResourceFileEngine);
if (d->resource.fileName().isEmpty()) {
qWarning("QResourceFileEngine::open: Missing file name");
@@ -1509,7 +1509,7 @@ QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngin
}
if (type & FlagsMask) {
ret |= ExistsFlag;
- if (d->resource.absoluteFilePath() == QLatin1String(":/"))
+ if (d->resource.absoluteFilePath() == ":/"_L1)
ret |= RootFlag;
}
return ret;
@@ -1519,24 +1519,24 @@ QString QResourceFileEngine::fileName(FileName file) const
{
Q_D(const QResourceFileEngine);
if (file == BaseName) {
- int slash = d->resource.fileName().lastIndexOf(QLatin1Char('/'));
+ const qsizetype slash = d->resource.fileName().lastIndexOf(u'/');
if (slash == -1)
return d->resource.fileName();
return d->resource.fileName().mid(slash + 1);
} else if (file == PathName || file == AbsolutePathName) {
const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath()
: d->resource.fileName();
- const int slash = path.lastIndexOf(QLatin1Char('/'));
+ const qsizetype slash = path.lastIndexOf(u'/');
if (slash == -1)
- return QLatin1String(":");
+ return ":"_L1;
else if (slash <= 1)
- return QLatin1String(":/");
+ return ":/"_L1;
return path.left(slash);
} else if (file == CanonicalName || file == CanonicalPathName) {
const QString absoluteFilePath = d->resource.absoluteFilePath();
if (file == CanonicalPathName) {
- const int slash = absoluteFilePath.lastIndexOf(QLatin1Char('/'));
+ const qsizetype slash = absoluteFilePath.lastIndexOf(u'/');
if (slash != -1)
return absoluteFilePath.left(slash);
}
@@ -1551,10 +1551,10 @@ uint QResourceFileEngine::ownerId(FileOwner) const
return nobodyID;
}
-QDateTime QResourceFileEngine::fileTime(FileTime time) const
+QDateTime QResourceFileEngine::fileTime(QFile::FileTime time) const
{
Q_D(const QResourceFileEngine);
- if (time == ModificationTime)
+ if (time == QFile::FileModificationTime)
return d->resource.lastModified();
return QDateTime();
}
@@ -1562,18 +1562,11 @@ QDateTime QResourceFileEngine::fileTime(FileTime time) const
/*!
\internal
*/
-QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters,
- const QStringList &filterNames)
+QAbstractFileEngine::IteratorUniquePtr
+QResourceFileEngine::beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames)
{
- return new QResourceFileEngineIterator(filters, filterNames);
-}
-
-/*!
- \internal
-*/
-QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList()
-{
- return nullptr;
+ return std::make_unique<QResourceFileEngineIterator>(path, filters, filterNames);
}
bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
@@ -1600,21 +1593,27 @@ bool QResourceFileEngine::supportsExtension(Extension extension) const
uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
{
Q_Q(QResourceFileEngine);
- Q_UNUSED(flags);
+ Q_ASSERT_X(resource.compressionAlgorithm() == QResource::NoCompression
+ || !uncompressed.isNull(), "QFile::map()",
+ "open() should have uncompressed compressed resources");
qint64 max = resource.uncompressedSize();
qint64 end;
if (offset < 0 || size <= 0 || !resource.isValid() ||
- add_overflow(offset, size, &end) || end > max) {
+ qAddOverflow(offset, size, &end) || end > max) {
q->setError(QFile::UnspecifiedError, QString());
return nullptr;
}
- const uchar *address = resource.data();
- if (resource.compressionAlgorithm() != QResource::NoCompression) {
- uncompress();
- if (uncompressed.isNull())
- return nullptr;
+ const uchar *address = reinterpret_cast<const uchar *>(uncompressed.constBegin());
+ if (!uncompressed.isNull())
+ return const_cast<uchar *>(address) + offset;
+
+ // resource was not compressed
+ address = resource.data();
+ if (flags & QFile::MapPrivateOption) {
+ // We need to provide read-write memory
+ mapUncompressed();
address = reinterpret_cast<const uchar *>(uncompressed.constData());
}
@@ -1635,6 +1634,15 @@ void QResourceFileEnginePrivate::uncompress() const
uncompressed = resource.uncompressedData();
}
+void QResourceFileEnginePrivate::mapUncompressed()
+{
+ Q_ASSERT(resource.compressionAlgorithm() == QResource::NoCompression);
+ if (!uncompressed.isNull())
+ return; // nothing to do
+ uncompressed = resource.uncompressedData();
+ uncompressed.detach();
+}
+
#endif // !defined(QT_BOOTSTRAPPED)
QT_END_NAMESPACE