Coverage Report

Created: 2026-05-16 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/image/qxbmhandler.cpp
Line
Count
Source
1
// Copyright (C) 2016 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
// Qt-Security score:critical reason:data-parser
4
5
#include <qplatformdefs.h>
6
#include "private/qxbmhandler_p.h"
7
8
#ifndef QT_NO_IMAGEFORMAT_XBM
9
10
#include <qimage.h>
11
#include <qiodevice.h>
12
#include <qloggingcategory.h>
13
#include <qvariant.h>
14
#include <private/qtools_p.h>
15
#include <private/qimage_p.h>
16
17
#include <cstdio>
18
19
#include <stdio.h>
20
21
QT_BEGIN_NAMESPACE
22
23
using namespace QtMiscUtils;
24
25
/*****************************************************************************
26
  X bitmap image read/write functions
27
 *****************************************************************************/
28
29
static inline int hex2byte(char *p)
30
0
{
31
0
    return QtMiscUtils::fromHex(p[0]) * 16 | QtMiscUtils::fromHex(p[1]);
32
0
}
33
34
static bool read_xbm_header(QIODevice *device, int& w, int& h)
35
0
{
36
0
    const int buflen = 300;
37
0
    const int maxlen = 4096;
38
0
    char buf[buflen + 1];
39
40
0
    qint64 readBytes = 0;
41
0
    qint64 totalReadBytes = 0;
42
43
0
    buf[0] = '\0';
44
45
    // skip initial comment, if any
46
0
    while (buf[0] != '#') {
47
0
        readBytes = device->readLine(buf, buflen);
48
49
        // if readBytes >= buflen, it's very probably not a C file
50
0
        if (readBytes <= 0 || readBytes >= buflen -1)
51
0
            return false;
52
53
        // limit xbm headers to the first 4k in the file to prevent
54
        // excessive reads on non-xbm files
55
0
        totalReadBytes += readBytes;
56
0
        if (totalReadBytes >= maxlen)
57
0
            return false;
58
0
    }
59
60
0
    auto parseDefine = [] (const char *buf, int len) -> int {
61
0
        auto checkChar = [] (char ch) -> bool {
62
0
            return isAsciiLetterOrNumber(ch)
63
0
                || ch == '_' || ch == '.';
64
0
        };
65
0
        auto isAsciiSpace = [] (char ch) -> bool {
66
0
            return ch == ' ' || ch == '\t';
67
0
        };
68
0
        const char define[] = "#define";
69
0
        constexpr size_t defineLen = sizeof(define) - 1;
70
0
        if (strncmp(buf, define, defineLen) != 0)
71
0
            return 0;
72
0
        int index = defineLen;
73
0
        while (buf[index] && isAsciiSpace(buf[index]))
74
0
            ++index;
75
0
        while (buf[index] && checkChar(buf[index]))
76
0
            ++index;
77
0
        while (buf[index] && isAsciiSpace(buf[index]))
78
0
            ++index;
79
80
0
        return QByteArray(buf + index, len - index).toInt();
81
0
    };
82
83
84
    // "#define .._width <num>"
85
0
    w = parseDefine(buf, readBytes - 1);
86
87
0
    readBytes = device->readLine(buf, buflen);
88
    // "#define .._height <num>"
89
0
    h = parseDefine(buf, readBytes - 1);
90
91
    // format error
92
0
    if (w <= 0 || w > 32767 || h <= 0 || h > 32767)
93
0
        return false;
94
95
0
    return true;
96
0
}
97
98
static bool read_xbm_body(QIODevice *device, int w, int h, QImage *outImage)
99
0
{
100
0
    const int buflen = 300;
101
0
    char buf[buflen + 1];
102
103
0
    qint64 readBytes = 0;
104
105
0
    char *p;
106
107
    // scan for database
108
0
    do {
109
0
        if ((readBytes = device->readLine(buf, buflen)) <= 0) {
110
            // end of file
111
0
            return false;
112
0
        }
113
114
0
        buf[readBytes] = '\0';
115
0
        p = strstr(buf, "0x");
116
0
    } while (!p);
117
118
0
    if (!QImageIOHandler::allocateImage(QSize(w, h), QImage::Format_MonoLSB, outImage))
119
0
        return false;
120
121
0
    outImage->fill(Qt::color0);       // in case the image data does not cover the full image
122
123
0
    outImage->setColorCount(2);
124
0
    outImage->setColor(0, qRgb(255,255,255));        // white
125
0
    outImage->setColor(1, qRgb(0,0,0));                // black
126
127
0
    int           x = 0, y = 0;
128
0
    uchar *b = outImage->scanLine(0);
129
0
    w = (w+7)/8;                                // byte width
130
131
0
    while (y < h) {                                // for all encoded bytes...
132
0
        if (p && p < (buf + readBytes - 3)) {      // p = "0x.."
133
0
            const int byte = hex2byte(p + 2);
134
0
            if (byte < 0) // non-hex char encountered
135
0
                return false;
136
0
            *b++ = byte;
137
0
            p += 2;
138
0
            if (++x == w && ++y < h) {
139
0
                b = outImage->scanLine(y);
140
0
                x = 0;
141
0
            }
142
0
            p = strstr(p, "0x");
143
0
        } else {                                // read another line
144
0
            if ((readBytes = device->readLine(buf,buflen)) <= 0)        // EOF ==> truncated image
145
0
                break;
146
0
            buf[readBytes] = '\0';
147
0
            p = strstr(buf, "0x");
148
0
        }
149
0
    }
150
151
0
    return true;
152
0
}
153
154
static bool read_xbm_image(QIODevice *device, QImage *outImage)
155
0
{
156
0
    int w = 0, h = 0;
157
0
    if (!read_xbm_header(device, w, h))
158
0
        return false;
159
0
    return read_xbm_body(device, w, h, outImage);
160
0
}
161
162
static bool write_xbm_image(const QImage &sourceImage, QIODevice *device, const QString &fileName)
163
0
{
164
0
    QImage image = sourceImage;
165
0
    int        w = image.width();
166
0
    int        h = image.height();
167
0
    int        i;
168
0
    const QByteArray s = fileName.toUtf8(); // get file base name
169
0
    const auto msize = s.size() + 100;
170
0
    char *buf = new char[msize];
171
172
0
    std::snprintf(buf, msize, "#define %s_width %d\n", s.data(), w);
173
0
    device->write(buf, qstrlen(buf));
174
0
    std::snprintf(buf, msize, "#define %s_height %d\n", s.data(), h);
175
0
    device->write(buf, qstrlen(buf));
176
0
    std::snprintf(buf, msize, "static char %s_bits[] = {\n ", s.data());
177
0
    device->write(buf, qstrlen(buf));
178
179
0
    if (image.format() != QImage::Format_MonoLSB)
180
0
        image = image.convertToFormat(QImage::Format_MonoLSB);
181
182
0
    bool invert = qGray(image.color(0)) < qGray(image.color(1));
183
0
    char hexrep[16];
184
0
    for (i=0; i<10; i++)
185
0
        hexrep[i] = '0' + i;
186
0
    for (i=10; i<16; i++)
187
0
        hexrep[i] = 'a' -10 + i;
188
0
    if (invert) {
189
0
        char t;
190
0
        for (i=0; i<8; i++) {
191
0
            t = hexrep[15-i];
192
0
            hexrep[15-i] = hexrep[i];
193
0
            hexrep[i] = t;
194
0
        }
195
0
    }
196
0
    int bcnt = 0;
197
0
    char *p = buf;
198
0
    int bpl = (w+7)/8;
199
0
    for (int y = 0; y < h; ++y) {
200
0
        const uchar *b = image.constScanLine(y);
201
0
        for (i = 0; i < bpl; ++i) {
202
0
            *p++ = '0'; *p++ = 'x';
203
0
            *p++ = hexrep[*b >> 4];
204
0
            *p++ = hexrep[*b++ & 0xf];
205
206
0
            if (i < bpl - 1 || y < h - 1) {
207
0
                *p++ = ',';
208
0
                if (++bcnt > 14) {
209
0
                    *p++ = '\n';
210
0
                    *p++ = ' ';
211
0
                    *p   = '\0';
212
0
                    if ((int)qstrlen(buf) != device->write(buf, qstrlen(buf))) {
213
0
                        delete [] buf;
214
0
                        return false;
215
0
                    }
216
0
                    p = buf;
217
0
                    bcnt = 0;
218
0
                }
219
0
            }
220
0
        }
221
0
    }
222
#ifdef Q_CC_MSVC
223
    strcpy_s(p, sizeof(" };\n"), " };\n");
224
#else
225
0
    strcpy(p, " };\n");
226
0
#endif
227
0
    if ((int)qstrlen(buf) != device->write(buf, qstrlen(buf))) {
228
0
        delete [] buf;
229
0
        return false;
230
0
    }
231
232
0
    delete [] buf;
233
0
    return true;
234
0
}
235
236
QXbmHandler::QXbmHandler()
237
0
    : state(Ready)
238
0
{
239
0
}
240
241
bool QXbmHandler::readHeader()
242
0
{
243
0
    state = Error;
244
0
    if (!read_xbm_header(device(), width, height))
245
0
        return false;
246
0
    state = ReadHeader;
247
0
    return true;
248
0
}
249
250
bool QXbmHandler::canRead() const
251
0
{
252
0
    if (state == Ready && !canRead(device()))
253
0
        return false;
254
255
0
    if (state != Error) {
256
0
        setFormat("xbm");
257
0
        return true;
258
0
    }
259
260
0
    return false;
261
0
}
262
263
bool QXbmHandler::canRead(QIODevice *device)
264
0
{
265
0
    if (!device) {
266
0
        qCWarning(lcImageIo, "QXbmHandler::canRead() called with no device");
267
0
        return false;
268
0
    }
269
270
    // it's impossible to tell whether we can load an XBM or not when
271
    // it's from a sequential device, as the only way to do it is to
272
    // attempt to parse the whole image.
273
0
    if (device->isSequential())
274
0
        return false;
275
276
0
    QImage image;
277
0
    qint64 oldPos = device->pos();
278
0
    bool success = read_xbm_image(device, &image);
279
0
    device->seek(oldPos);
280
281
0
    return success;
282
0
}
283
284
bool QXbmHandler::read(QImage *image)
285
0
{
286
0
    if (state == Error)
287
0
        return false;
288
289
0
    if (state == Ready && !readHeader()) {
290
0
        state = Error;
291
0
        return false;
292
0
    }
293
294
0
    if (!read_xbm_body(device(), width, height, image)) {
295
0
        state = Error;
296
0
        return false;
297
0
    }
298
299
0
    state = Ready;
300
0
    return true;
301
0
}
302
303
bool QXbmHandler::write(const QImage &image)
304
0
{
305
0
    return write_xbm_image(image, device(), fileName);
306
0
}
307
308
bool QXbmHandler::supportsOption(ImageOption option) const
309
0
{
310
0
    return option == Name
311
0
        || option == Size
312
0
        || option == ImageFormat;
313
0
}
314
315
QVariant QXbmHandler::option(ImageOption option) const
316
0
{
317
0
    if (option == Name) {
318
0
        return fileName;
319
0
    } else if (option == Size) {
320
0
        if (state == Error)
321
0
            return QVariant();
322
0
        if (state == Ready && !const_cast<QXbmHandler*>(this)->readHeader())
323
0
            return QVariant();
324
0
        return QSize(width, height);
325
0
    } else if (option == ImageFormat) {
326
0
        return QImage::Format_MonoLSB;
327
0
    }
328
0
    return QVariant();
329
0
}
330
331
void QXbmHandler::setOption(ImageOption option, const QVariant &value)
332
0
{
333
0
    if (option == Name)
334
0
        fileName = value.toString();
335
0
}
336
337
QT_END_NAMESPACE
338
339
#endif // QT_NO_IMAGEFORMAT_XBM