Coverage Report

Created: 2026-04-01 07:24

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
1.33k
{
31
1.33k
    return QtMiscUtils::fromHex(p[0]) * 16 | QtMiscUtils::fromHex(p[1]);
32
1.33k
}
33
34
static bool read_xbm_header(QIODevice *device, int& w, int& h)
35
3.68k
{
36
3.68k
    const int buflen = 300;
37
3.68k
    const int maxlen = 4096;
38
3.68k
    char buf[buflen + 1];
39
40
3.68k
    qint64 readBytes = 0;
41
3.68k
    qint64 totalReadBytes = 0;
42
43
3.68k
    buf[0] = '\0';
44
45
    // skip initial comment, if any
46
24.7k
    while (buf[0] != '#') {
47
23.9k
        readBytes = device->readLine(buf, buflen);
48
49
        // if readBytes >= buflen, it's very probably not a C file
50
23.9k
        if (readBytes <= 0 || readBytes >= buflen -1)
51
2.81k
            return false;
52
53
        // limit xbm headers to the first 4k in the file to prevent
54
        // excessive reads on non-xbm files
55
21.1k
        totalReadBytes += readBytes;
56
21.1k
        if (totalReadBytes >= maxlen)
57
4
            return false;
58
21.1k
    }
59
60
1.72k
    auto parseDefine = [] (const char *buf, int len) -> int {
61
2.62k
        auto checkChar = [] (char ch) -> bool {
62
2.62k
            return isAsciiLetterOrNumber(ch)
63
1.44k
                || ch == '_' || ch == '.';
64
2.62k
        };
65
3.37k
        auto isAsciiSpace = [] (char ch) -> bool {
66
3.37k
            return ch == ' ' || ch == '\t';
67
3.37k
        };
68
1.72k
        const char define[] = "#define";
69
1.72k
        constexpr size_t defineLen = sizeof(define) - 1;
70
1.72k
        if (strncmp(buf, define, defineLen) != 0)
71
625
            return 0;
72
1.10k
        int index = defineLen;
73
1.58k
        while (buf[index] && isAsciiSpace(buf[index]))
74
481
            ++index;
75
2.66k
        while (buf[index] && checkChar(buf[index]))
76
1.55k
            ++index;
77
1.85k
        while (buf[index] && isAsciiSpace(buf[index]))
78
748
            ++index;
79
80
1.10k
        return QByteArray(buf + index, len - index).toInt();
81
1.72k
    };
82
83
84
    // "#define .._width <num>"
85
864
    w = parseDefine(buf, readBytes - 1);
86
87
864
    readBytes = device->readLine(buf, buflen);
88
    // "#define .._height <num>"
89
864
    h = parseDefine(buf, readBytes - 1);
90
91
    // format error
92
864
    if (w <= 0 || w > 32767 || h <= 0 || h > 32767)
93
615
        return false;
94
95
249
    return true;
96
864
}
97
98
static bool read_xbm_body(QIODevice *device, int w, int h, QImage *outImage)
99
249
{
100
249
    const int buflen = 300;
101
249
    char buf[buflen + 1];
102
103
249
    qint64 readBytes = 0;
104
105
249
    char *p;
106
107
    // scan for database
108
484
    do {
109
484
        if ((readBytes = device->readLine(buf, buflen)) <= 0) {
110
            // end of file
111
18
            return false;
112
18
        }
113
114
466
        buf[readBytes] = '\0';
115
466
        p = strstr(buf, "0x");
116
466
    } while (!p);
117
118
231
    if (!QImageIOHandler::allocateImage(QSize(w, h), QImage::Format_MonoLSB, outImage))
119
1
        return false;
120
121
230
    outImage->fill(Qt::color0);       // in case the image data does not cover the full image
122
123
230
    outImage->setColorCount(2);
124
230
    outImage->setColor(0, qRgb(255,255,255));        // white
125
230
    outImage->setColor(1, qRgb(0,0,0));                // black
126
127
230
    int           x = 0, y = 0;
128
230
    uchar *b = outImage->scanLine(0);
129
230
    w = (w+7)/8;                                // byte width
130
131
16.3k
    while (y < h) {                                // for all encoded bytes...
132
16.2k
        if (p && p < (buf + readBytes - 3)) {      // p = "0x.."
133
1.33k
            const int byte = hex2byte(p + 2);
134
1.33k
            if (byte < 0) // non-hex char encountered
135
44
                return false;
136
1.29k
            *b++ = byte;
137
1.29k
            p += 2;
138
1.29k
            if (++x == w && ++y < h) {
139
523
                b = outImage->scanLine(y);
140
523
                x = 0;
141
523
            }
142
1.29k
            p = strstr(p, "0x");
143
14.9k
        } else {                                // read another line
144
14.9k
            if ((readBytes = device->readLine(buf,buflen)) <= 0)        // EOF ==> truncated image
145
170
                break;
146
14.7k
            buf[readBytes] = '\0';
147
14.7k
            p = strstr(buf, "0x");
148
14.7k
        }
149
16.2k
    }
150
151
186
    return true;
152
230
}
153
154
static bool read_xbm_image(QIODevice *device, QImage *outImage)
155
3.58k
{
156
3.58k
    int w = 0, h = 0;
157
3.58k
    if (!read_xbm_header(device, w, h))
158
3.43k
        return false;
159
156
    return read_xbm_body(device, w, h, outImage);
160
3.58k
}
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
93
    : state(Ready)
238
93
{
239
93
}
240
241
bool QXbmHandler::readHeader()
242
93
{
243
93
    state = Error;
244
93
    if (!read_xbm_header(device(), width, height))
245
0
        return false;
246
93
    state = ReadHeader;
247
93
    return true;
248
93
}
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
3.58k
{
265
3.58k
    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
3.58k
    if (device->isSequential())
274
0
        return false;
275
276
3.58k
    QImage image;
277
3.58k
    qint64 oldPos = device->pos();
278
3.58k
    bool success = read_xbm_image(device, &image);
279
3.58k
    device->seek(oldPos);
280
281
3.58k
    return success;
282
3.58k
}
283
284
bool QXbmHandler::read(QImage *image)
285
93
{
286
93
    if (state == Error)
287
0
        return false;
288
289
93
    if (state == Ready && !readHeader()) {
290
0
        state = Error;
291
0
        return false;
292
0
    }
293
294
93
    if (!read_xbm_body(device(), width, height, image)) {
295
0
        state = Error;
296
0
        return false;
297
0
    }
298
299
93
    state = Ready;
300
93
    return true;
301
93
}
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
372
{
310
372
    return option == Name
311
372
        || option == Size
312
372
        || option == ImageFormat;
313
372
}
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