Revision control

Copy as Markdown

// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qsharedimageloader_p.h"
#include <private/qobject_p.h>
#include <private/qimage_p.h>
#include <QtCore/qpointer.h>
#include <QSharedMemory>
#include <memory>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcSharedImage, "qt.quick.sharedimage");
struct SharedImageHeader {
quint8 magic;
quint8 version;
quint16 offset;
qint32 width;
qint32 height;
qint32 bpl;
QImage::Format format;
};
Q_STATIC_ASSERT(sizeof(SharedImageHeader) % 4 == 0);
#if QT_CONFIG(sharedmemory)
struct SharedImageInfo {
QString path;
QPointer<QSharedMemory> shmp;
};
void cleanupSharedImage(void *cleanupInfo)
{
if (!cleanupInfo)
return;
SharedImageInfo *sii = static_cast<SharedImageInfo *>(cleanupInfo);
qCDebug(lcSharedImage) << "Cleanup called for" << sii->path;
if (sii->shmp.isNull()) {
qCDebug(lcSharedImage) << "shm is 0 for" << sii->path;
return;
}
QSharedMemory *shm = sii->shmp.data();
sii->shmp.clear();
delete shm; // destructor detaches
delete sii;
}
#else
void cleanupSharedImage(void *) {}
#endif
class QSharedImageLoaderPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QSharedImageLoader)
public:
QSharedImageLoaderPrivate() {}
QImage load(const QString &path, QSharedImageLoader::ImageParameters *params);
void storeImageToMem(void *data, const QImage &img);
bool verifyMem(const void *data, int size);
QImage createImageFromMem(const void *data, void *cleanupInfo);
};
void QSharedImageLoaderPrivate::storeImageToMem(void *data, const QImage &img)
{
Q_ASSERT(data && !img.isNull());
SharedImageHeader *h = static_cast<SharedImageHeader *>(data);
h->magic = 'Q';
h->version = 1;
h->offset = sizeof(SharedImageHeader);
h->width = img.width();
h->height = img.height();
h->bpl = img.bytesPerLine();
h->format = img.format();
uchar *p = static_cast<uchar *>(data) + sizeof(SharedImageHeader);
memcpy(p, img.constBits(), img.sizeInBytes());
}
bool QSharedImageLoaderPrivate::verifyMem(const void *data, int size)
{
if (!data || size < int(sizeof(SharedImageHeader)))
return false;
const SharedImageHeader *h = static_cast<const SharedImageHeader *>(data);
if ((h->magic != 'Q')
|| (h->version < 1)
|| (h->offset < sizeof(SharedImageHeader))
|| (h->width <= 0)
|| (h->height <= 0)
|| (h->bpl <= 0)
|| (h->format <= QImage::Format_Invalid)
|| (h->format >= QImage::NImageFormats)) {
return false;
}
const int depth = qt_depthForFormat(h->format);
if (h->bpl < (qint64(h->width) * depth + 7) / 8)
return false;
qint64 availSize = size - h->offset;
if (qint64(h->height) * h->bpl > availSize)
return false;
return true;
}
QImage QSharedImageLoaderPrivate::createImageFromMem(const void *data, void *cleanupInfo)
{
const SharedImageHeader *h = static_cast<const SharedImageHeader *>(data);
const uchar *p = static_cast<const uchar *>(data) + h->offset;
QImage img(p, h->width, h->height, h->bpl, h->format, cleanupSharedImage, cleanupInfo);
return img;
}
QImage QSharedImageLoaderPrivate::load(const QString &path, QSharedImageLoader::ImageParameters *params)
{
#if QT_CONFIG(sharedmemory)
Q_Q(QSharedImageLoader);
QImage nil;
if (path.isEmpty())
return nil;
auto shm = std::make_unique<QSharedMemory>(QSharedMemory::legacyNativeKey(q->key(path, params)));
bool locked = false;
if (!shm->attach(QSharedMemory::ReadOnly)) {
QImage img = q->loadFile(path, params);
if (img.isNull())
return nil;
size_t size = sizeof(SharedImageHeader) + img.sizeInBytes();
if (size > size_t(std::numeric_limits<int>::max())) {
qCDebug(lcSharedImage) << "Image" << path << "to large to load";
return nil;
} else if (shm->create(int(size))) {
qCDebug(lcSharedImage) << "Created new shm segment of size" << size << "for image" << path;
if (!shm->lock()) {
qCDebug(lcSharedImage) << "Lock1 failed!?" << shm->errorString();
return nil;
}
locked = true;
storeImageToMem(shm->data(), img);
} else if (shm->error() == QSharedMemory::AlreadyExists) {
// race handling: other process may have created the share while
// we loaded the image, so try again to just attach
if (!shm->attach(QSharedMemory::ReadOnly)) {
qCDebug(lcSharedImage) << "Attach to existing failed?" << shm->errorString();
return nil;
}
} else {
qCDebug(lcSharedImage) << "Create failed?" << shm->errorString();
return nil;
}
}
Q_ASSERT(shm->isAttached());
if (!locked) {
if (!shm->lock()) {
qCDebug(lcSharedImage) << "Lock2 failed!?" << shm->errorString();
return nil;
}
locked = true;
}
if (!verifyMem(shm->constData(), shm->size())) {
qCDebug(lcSharedImage) << "Verifymem failed!?";
shm->unlock();
return nil;
}
QSharedMemory *shmp = shm.release();
SharedImageInfo *sii = new SharedImageInfo;
sii->path = path;
sii->shmp = shmp;
QImage shImg = createImageFromMem(shmp->constData(), sii);
if (!shmp->unlock()) {
qCDebug(lcSharedImage) << "UnLock failed!?";
}
return shImg;
#else
Q_UNUSED(path);
Q_UNUSED(params);
return QImage();
#endif
}
QSharedImageLoader::QSharedImageLoader(QObject *parent)
: QObject(*new QSharedImageLoaderPrivate, parent)
{
}
QSharedImageLoader::~QSharedImageLoader()
{
}
QImage QSharedImageLoader::load(const QString &path, ImageParameters *params)
{
Q_D(QSharedImageLoader);
return d->load(path, params);
}
QImage QSharedImageLoader::loadFile(const QString &path, ImageParameters *params)
{
Q_UNUSED(params);
return QImage(path);
}
QString QSharedImageLoader::key(const QString &path, ImageParameters *params)
{
Q_UNUSED(params);
return path;
}
QT_END_NAMESPACE
#include "moc_qsharedimageloader_p.cpp"