Compare commits

..

3 Commits

Author SHA1 Message Date
f5494d9acf iff: Support ILBM (PBM)
It's another variant of IFF where the pixel data is stored
contiguously rather than interleaved.
2025-07-06 16:24:55 +02:00
ea1983a7d1 IFF: Fix possible stack overflow 2025-07-05 09:59:06 +00:00
775b53f1f6 Update version to 6.17.0 2025-07-04 18:28:45 +02:00
5 changed files with 75 additions and 26 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.16.0") # handled by release scripts set(KF_VERSION "6.17.0") # handled by release scripts
set(KF_DEP_VERSION "6.16.0") # handled by release scripts set(KF_DEP_VERSION "6.16.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION}) project(KImageFormats VERSION ${KF_VERSION})

View File

@ -10,6 +10,8 @@
#include <QDebug> #include <QDebug>
#define RECURSION_PROTECTION 10
IFFChunk::~IFFChunk() IFFChunk::~IFFChunk()
{ {
@ -20,6 +22,7 @@ IFFChunk::IFFChunk()
, _size{0} , _size{0}
, _align{2} , _align{2}
, _dataPos{0} , _dataPos{0}
, _recursionCnt{0}
{ {
} }
@ -49,12 +52,16 @@ qint32 IFFChunk::alignBytes() const
bool IFFChunk::readStructure(QIODevice *d) bool IFFChunk::readStructure(QIODevice *d)
{ {
auto ok = readInfo(d); auto ok = readInfo(d);
ok = ok && innerReadStructure(d); if (recursionCounter() > RECURSION_PROTECTION - 1) {
ok = ok && IFFChunk::innerReadStructure(d); // force default implementation (no more recursion)
} else {
ok = ok && innerReadStructure(d);
}
if (ok) { if (ok) {
auto pos = _dataPos + _size; auto pos = _dataPos + _size;
if (auto align = pos % alignBytes()) if (auto align = pos % alignBytes())
pos += alignBytes() - align; pos += alignBytes() - align;
ok = d->seek(pos); ok = pos < d->pos() ? false : d->seek(pos);
} }
return ok; return ok;
} }
@ -169,7 +176,17 @@ void IFFChunk::setChunks(const ChunkList &chunks)
_chunks = chunks; _chunks = chunks;
} }
IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes) qint32 IFFChunk::recursionCounter() const
{
return _recursionCnt;
}
void IFFChunk::setRecursionCounter(qint32 cnt)
{
_recursionCnt = cnt;
}
IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes, qint32 recursionCnt)
{ {
auto tmp = false; auto tmp = false;
if (ok == nullptr) { if (ok == nullptr) {
@ -181,6 +198,10 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
return {}; return {};
} }
if (recursionCnt > RECURSION_PROTECTION) {
return {};
}
IFFChunk::ChunkList list; IFFChunk::ChunkList list;
for (; !d->atEnd();) { for (; !d->atEnd();) {
auto cid = d->peek(4); auto cid = d->peek(4);
@ -229,6 +250,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
chunk->setAlignBytes(alignBytes); chunk->setAlignBytes(alignBytes);
} }
chunk->setRecursionCounter(recursionCnt + 1);
if (!chunk->readStructure(d)) { if (!chunk->readStructure(d)) {
*ok = false; *ok = false;
return {}; return {};
@ -243,7 +265,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok) IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok)
{ {
return innerFromDevice(d, ok, 2); return innerFromDevice(d, ok, 2, 0);
} }
@ -525,9 +547,9 @@ bool BODYChunk::isValid() const
return chunkId() == BODYChunk::defaultChunkId(); return chunkId() == BODYChunk::defaultChunkId();
} }
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) const QByteArray BODYChunk::strideRead(QIODevice *d, const FORMChunk *form, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) const
{ {
if (!isValid() || header == nullptr) { if (!isValid() || form == nullptr || header == nullptr) {
return {}; return {};
} }
@ -549,7 +571,11 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
auto planes = _readBuffer.left(readSize); auto planes = _readBuffer.left(readSize);
_readBuffer.remove(0, readSize); _readBuffer.remove(0, readSize);
return BODYChunk::deinterleave(planes, header, camg, cmap); if (form->formType() == FORMChunk::FormType::Pbm) {
return planes;
} else {
return BODYChunk::deinterleave(planes, header, camg, cmap);
}
} }
bool BODYChunk::resetStrideRead(QIODevice *d) const bool BODYChunk::resetStrideRead(QIODevice *d) const
@ -751,15 +777,21 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
if (bytes() < 4) { if (bytes() < 4) {
return false; return false;
} }
_type = d->read(4); const auto type = d->read(4);
auto ok = true; auto ok = false;
if (_type == QByteArray("ILBM")) { if (type == QByteArrayLiteral("ILBM")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes())); _type = FormType::Ilbm;
} else if (type == QByteArrayLiteral("PBM ")) {
_type = FormType::Pbm;
}
if (_type != FormType::Unknown) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
} }
return ok; return ok;
} }
QByteArray FORMChunk::formType() const FORMChunk::FormType FORMChunk::formType() const
{ {
return _type; return _type;
} }
@ -857,9 +889,9 @@ bool FOR4Chunk::innerReadStructure(QIODevice *d)
_type = d->read(4); _type = d->read(4);
auto ok = true; auto ok = true;
if (_type == QByteArray("CIMG")) { if (_type == QByteArray("CIMG")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes())); setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
} else if (_type == QByteArray("TBMP")) { } else if (_type == QByteArray("TBMP")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes())); setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
} }
return ok; return ok;
} }

View File

@ -256,6 +256,14 @@ protected:
*/ */
void setChunks(const ChunkList &chunks); void setChunks(const ChunkList &chunks);
/*!
* \brief recursionCounter
* Protection against stack overflow due to broken data.
* \return The current recursion counter.
*/
qint32 recursionCounter() const;
void setRecursionCounter(qint32 cnt);
inline quint16 ui16(quint8 c1, quint8 c2) const { inline quint16 ui16(quint8 c1, quint8 c2) const {
return (quint16(c2) << 8) | quint16(c1); return (quint16(c2) << 8) | quint16(c1);
} }
@ -272,7 +280,7 @@ protected:
return qint32(ui32(c1, c2, c3, c4)); return qint32(ui32(c1, c2, c3, c4));
} }
static ChunkList innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes); static ChunkList innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes, qint32 recursionCnt);
private: private:
char _chunkId[4]; char _chunkId[4];
@ -287,6 +295,8 @@ private:
ChunkList _chunks; ChunkList _chunks;
qint32 _recursionCnt;
}; };
@ -441,6 +451,7 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override; virtual bool innerReadStructure(QIODevice *d) override;
}; };
class FORMChunk;
/*! /*!
* \brief The BODYChunk class * \brief The BODYChunk class
@ -466,7 +477,7 @@ public:
* \return The scanline as requested for QImage. * \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one. * \warning Call resetStrideRead() once before this one.
*/ */
QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const; QByteArray strideRead(QIODevice *d, const FORMChunk *form, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
/*! /*!
* \brief resetStrideRead * \brief resetStrideRead
@ -490,9 +501,13 @@ private:
*/ */
class FORMChunk : public IFFChunk class FORMChunk : public IFFChunk
{ {
QByteArray _type;
public: public:
enum class FormType {
Unknown,
Ilbm,
Pbm,
};
virtual ~FORMChunk() override; virtual ~FORMChunk() override;
FORMChunk(); FORMChunk();
FORMChunk(const FORMChunk& other) = default; FORMChunk(const FORMChunk& other) = default;
@ -502,7 +517,7 @@ public:
bool isSupported() const; bool isSupported() const;
QByteArray formType() const; FormType formType() const;
QImage::Format format() const; QImage::Format format() const;
@ -512,8 +527,10 @@ public:
protected: protected:
virtual bool innerReadStructure(QIODevice *d) override; virtual bool innerReadStructure(QIODevice *d) override;
};
private:
FormType _type = FormType::Unknown;
};
/*! /*!
* \brief The FOR4Chunk class * \brief The FOR4Chunk class

View File

@ -74,7 +74,7 @@ IFFHandler::IFFHandler()
bool IFFHandler::canRead() const bool IFFHandler::canRead() const
{ {
if (canRead(device())) { if (canRead(device())) {
setFormat("iff"); setFormat("iff"); // TODO ilbm based on header?
return true; return true;
} }
return false; return false;
@ -192,7 +192,7 @@ bool IFFHandler::readStandardImage(QImage *image)
} }
for (auto y = 0, h = img.height(); y < h; ++y) { for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y)); auto line = reinterpret_cast<char*>(img.scanLine(y));
auto ba = body->strideRead(device(), header, camg, cmap); auto ba = body->strideRead(device(), form, header, camg, cmap);
if (ba.isEmpty()) { if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline"; qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
return false; return false;
@ -334,7 +334,7 @@ QVariant IFFHandler::option(ImageOption option) const
QImageIOPlugin::Capabilities IFFPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities IFFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{ {
if (format == "iff") { if (format == "iff" || format == "ilbm") {
return Capabilities(CanRead); return Capabilities(CanRead);
} }
if (!format.isEmpty()) { if (!format.isEmpty()) {

View File

@ -1,4 +1,4 @@
{ {
"Keys": [ "iff" ], "Keys": [ "iff", "ilbm" ],
"MimeTypes": [ "application/x-iff" ] "MimeTypes": [ "application/x-iff", "application/x-ilbm" ]
} }