Coverage Report

Created: 2026-08-11 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/xml/dom/qdomhelpers.cpp
Line
Count
Source
1
// Copyright (C) 2019 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 <QtXml/qtxmlglobal.h>
6
7
#if QT_CONFIG(dom)
8
9
#include "qdomhelpers_p.h"
10
#include "qdom_p.h"
11
12
#include <QtCore/qshareddata.h>
13
#include "qxmlstream.h"
14
#include "private/qxmlstream_p.h"
15
16
#include <memory>
17
#include <stack>
18
19
QT_BEGIN_NAMESPACE
20
21
using namespace Qt::StringLiterals;
22
23
template <typename T, typename...Args>
24
static QExplicitlySharedDataPointer<T> qdom_make_esdp(Args&&...args)
25
0
{
26
0
    QExplicitlySharedDataPointer<T> dp{
27
0
        new T(std::forward<Args>(args)...),
28
0
        QAdoptSharedDataTag{}
29
0
    };
30
0
    Q_ASSERT(dp->ref.loadRelaxed() == 1);
31
0
    return dp;
32
0
}
33
34
/**************************************************************
35
 *
36
 * QDomBuilder
37
 *
38
 **************************************************************/
39
40
QDomBuilder::QDomBuilder(QDomDocumentPrivate *d, QXmlStreamReader *r,
41
                         QDomDocument::ParseOptions options)
42
20.0k
    : doc(d), node(d), reader(r), parseOptions(options)
43
20.0k
{
44
20.0k
    Q_ASSERT(doc);
45
20.0k
    Q_ASSERT(reader);
46
20.0k
}
47
48
20.0k
QDomBuilder::~QDomBuilder() {}
49
50
bool QDomBuilder::endDocument()
51
0
{
52
    // ### is this really necessary? (rms)
53
0
    if (node != doc)
54
0
        return false;
55
0
    return true;
56
0
}
57
58
bool QDomBuilder::startDTD(const QString &name, const QString &publicId, const QString &systemId)
59
228
{
60
228
    doc->doctype()->name = name;
61
228
    doc->doctype()->publicId = publicId;
62
228
    doc->doctype()->systemId = systemId;
63
228
    return true;
64
228
}
65
66
QString QDomBuilder::dtdInternalSubset(const QString &dtd)
67
228
{
68
    // https://www.w3.org/TR/xml/#NT-intSubset
69
    // doctypedecl: '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>'
70
228
    const QString &name = doc->doctype()->name;
71
228
    QStringView tmp = QStringView(dtd).sliced(dtd.indexOf(name) + name.size());
72
73
228
    const QString &publicId = doc->doctype()->publicId;
74
228
    if (!publicId.isEmpty())
75
22
        tmp = tmp.sliced(tmp.indexOf(publicId) + publicId.size());
76
77
228
    const QString &systemId = doc->doctype()->systemId;
78
228
    if (!systemId.isEmpty())
79
15
        tmp = tmp.sliced(tmp.indexOf(systemId) + systemId.size());
80
81
228
    const qsizetype obra = tmp.indexOf(u'[');
82
228
    const qsizetype cbra = tmp.lastIndexOf(u']');
83
228
    if (obra >= 0 && cbra >= 0)
84
2
        return tmp.left(cbra).sliced(obra + 1).toString();
85
86
226
    return QString();
87
228
}
88
89
bool QDomBuilder::parseDTD(const QString &dtd)
90
228
{
91
228
    doc->doctype()->internalSubset = dtdInternalSubset(dtd);
92
228
    return true;
93
228
}
94
95
bool QDomBuilder::startElement(const QString &nsURI, const QString &qName,
96
                               const QXmlStreamAttributes &atts)
97
478k
{
98
478k
    const bool nsProcessing =
99
478k
            parseOptions.testFlag(QDomDocument::ParseOption::UseNamespaceProcessing);
100
478k
    QDomNodePrivate *n =
101
478k
            nsProcessing ? doc->createElementNS(nsURI, qName) : doc->createElement(qName);
102
478k
    if (!n)
103
0
        return false;
104
105
478k
    n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
106
107
478k
    node->appendChild(n);
108
478k
    node = n;
109
110
    // attributes
111
478k
    for (const auto &attr : atts) {
112
45.6k
        auto domElement = static_cast<QDomElementPrivate *>(node);
113
45.6k
        if (nsProcessing) {
114
45.6k
            domElement->setAttributeNS(attr.namespaceUri().toString(),
115
45.6k
                                       attr.qualifiedName().toString(),
116
45.6k
                                       attr.value().toString());
117
45.6k
        } else {
118
0
            domElement->setAttribute(attr.qualifiedName().toString(),
119
0
                                     attr.value().toString());
120
0
        }
121
45.6k
    }
122
123
478k
    return true;
124
478k
}
125
126
bool QDomBuilder::endElement()
127
64.2k
{
128
64.2k
    if (!node || node == doc)
129
0
        return false;
130
64.2k
    node = node->parent();
131
132
64.2k
    return true;
133
64.2k
}
134
135
bool QDomBuilder::characters(const QString &characters, bool cdata)
136
83.7k
{
137
    // No text as child of some document
138
83.7k
    if (node == doc)
139
0
        return false;
140
141
83.7k
    QExplicitlySharedDataPointer<QDomNodePrivate> n;
142
83.7k
    if (cdata) {
143
0
        n.reset(doc->createCDATASection(characters));
144
83.7k
    } else if (!entityName.isEmpty()) {
145
0
        auto e = qdom_make_esdp<QDomEntityPrivate>(
146
0
                    doc, nullptr, entityName, QString(), QString(), QString());
147
0
        e->value = characters;
148
0
        doc->doctype()->appendChild(e.get());
149
0
        e.reset(); // reaps unless appendChild() adopted
150
0
        n.reset(doc->createEntityReference(entityName));
151
83.7k
    } else {
152
83.7k
        n.reset(doc->createTextNode(characters));
153
83.7k
    }
154
83.7k
    if (!n)
155
34
        return false;
156
83.6k
    n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
157
83.6k
    node->appendChild(n.get());
158
159
83.6k
    return true;
160
83.7k
}
161
162
bool QDomBuilder::processingInstruction(const QString &target, const QString &data)
163
22.9k
{
164
22.9k
    QDomNodePrivate *n;
165
22.9k
    n = doc->createProcessingInstruction(target, data);
166
22.9k
    if (n) {
167
22.8k
        n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
168
22.8k
        node->appendChild(n);
169
22.8k
        return true;
170
22.8k
    } else
171
41
        return false;
172
22.9k
}
173
174
bool QDomBuilder::skippedEntity(const QString &name)
175
0
{
176
0
    QDomNodePrivate *n = doc->createEntityReference(name);
177
0
    if (!n)
178
0
        return false;
179
0
    n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
180
0
    node->appendChild(n);
181
0
    return true;
182
0
}
183
184
void QDomBuilder::fatalError(const QString &message)
185
19.4k
{
186
19.4k
    parseResult.errorMessage = message;
187
19.4k
    parseResult.errorLine = reader->lineNumber();
188
19.4k
    parseResult.errorColumn = reader->columnNumber();
189
19.4k
}
190
191
bool QDomBuilder::startEntity(const QString &name)
192
0
{
193
0
    entityName = name;
194
0
    return true;
195
0
}
196
197
bool QDomBuilder::endEntity()
198
0
{
199
0
    entityName.clear();
200
0
    return true;
201
0
}
202
203
bool QDomBuilder::comment(const QString &characters)
204
1.21k
{
205
1.21k
    QDomNodePrivate *n;
206
1.21k
    n = doc->createComment(characters);
207
1.21k
    if (!n)
208
1
        return false;
209
1.21k
    n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
210
1.21k
    node->appendChild(n);
211
1.21k
    return true;
212
1.21k
}
213
214
bool QDomBuilder::unparsedEntityDecl(const QString &name, const QString &publicId,
215
                                     const QString &systemId, const QString &notationName)
216
0
{
217
0
    QDomEntityPrivate *e =
218
0
            new QDomEntityPrivate(doc, nullptr, name, publicId, systemId, notationName);
219
    // keep the refcount balanced: appendChild() does a ref anyway.
220
0
    e->ref.deref();
221
0
    doc->doctype()->appendChild(e);
222
0
    return true;
223
0
}
224
225
bool QDomBuilder::externalEntityDecl(const QString &name, const QString &publicId,
226
                                     const QString &systemId)
227
0
{
228
0
    return unparsedEntityDecl(name, publicId, systemId, QString());
229
0
}
230
231
bool QDomBuilder::notationDecl(const QString &name, const QString &publicId,
232
                               const QString &systemId)
233
0
{
234
0
    QDomNotationPrivate *n = new QDomNotationPrivate(doc, nullptr, name, publicId, systemId);
235
    // keep the refcount balanced: appendChild() does a ref anyway.
236
0
    n->ref.deref();
237
0
    doc->doctype()->appendChild(n);
238
0
    return true;
239
0
}
240
241
/**************************************************************
242
 *
243
 * QDomParser
244
 *
245
 **************************************************************/
246
247
QDomParser::QDomParser(QDomDocumentPrivate *d, QXmlStreamReader *r,
248
                       QDomDocument::ParseOptions options)
249
20.0k
    : reader(r), domBuilder(d, r, options)
250
20.0k
{
251
20.0k
}
252
253
bool QDomParser::parse()
254
20.0k
{
255
20.0k
    return parseProlog() && parseBody();
256
20.0k
}
257
258
bool QDomParser::parseProlog()
259
20.0k
{
260
20.0k
    Q_ASSERT(reader);
261
262
20.0k
    bool foundDtd = false;
263
264
37.0k
    while (!reader->atEnd()) {
265
37.0k
        reader->readNext();
266
267
37.0k
        if (reader->hasError()) {
268
13.8k
            domBuilder.fatalError(reader->errorString());
269
13.8k
            return false;
270
13.8k
        }
271
272
23.2k
        switch (reader->tokenType()) {
273
12.7k
        case QXmlStreamReader::StartDocument:
274
12.7k
            if (!reader->documentVersion().isEmpty()) {
275
7.12k
                QString value(u"version='"_s);
276
7.12k
                value += reader->documentVersion();
277
7.12k
                value += u'\'';
278
7.12k
                if (!reader->documentEncoding().isEmpty()) {
279
7.08k
                    value += u" encoding='"_s;
280
7.08k
                    value += reader->documentEncoding();
281
7.08k
                    value += u'\'';
282
7.08k
                }
283
7.12k
                if (reader->isStandaloneDocument()) {
284
2.30k
                    value += u" standalone='yes'"_s;
285
4.82k
                } else {
286
                    // Add the standalone attribute only if it was specified
287
4.82k
                    if (reader->hasStandaloneDeclaration())
288
870
                        value += u" standalone='no'"_s;
289
4.82k
                }
290
291
7.12k
                if (!domBuilder.processingInstruction(u"xml"_s, value)) {
292
0
                    domBuilder.fatalError(
293
0
                            QDomParser::tr("Error occurred while processing XML declaration"));
294
0
                    return false;
295
0
                }
296
7.12k
            }
297
12.7k
            break;
298
12.7k
        case QXmlStreamReader::DTD:
299
228
            if (foundDtd) {
300
0
                domBuilder.fatalError(QDomParser::tr("Multiple DTD sections are not allowed"));
301
0
                return false;
302
0
            }
303
228
            foundDtd = true;
304
305
228
            if (!domBuilder.startDTD(reader->dtdName().toString(),
306
228
                                     reader->dtdPublicId().toString(),
307
228
                                     reader->dtdSystemId().toString())) {
308
0
                domBuilder.fatalError(
309
0
                        QDomParser::tr("Error occurred while processing document type declaration"));
310
0
                return false;
311
0
            }
312
228
            if (!domBuilder.parseDTD(reader->text().toString()))
313
0
                return false;
314
228
            if (!parseMarkupDecl())
315
0
                return false;
316
228
            break;
317
848
        case QXmlStreamReader::Comment:
318
848
            if (!domBuilder.comment(reader->text().toString())) {
319
0
                domBuilder.fatalError(QDomParser::tr("Error occurred while processing comment"));
320
0
                return false;
321
0
            }
322
848
            break;
323
3.26k
        case QXmlStreamReader::ProcessingInstruction:
324
3.26k
            if (!domBuilder.processingInstruction(reader->processingInstructionTarget().toString(),
325
3.26k
                                                  reader->processingInstructionData().toString())) {
326
35
                domBuilder.fatalError(
327
35
                        QDomParser::tr("Error occurred while processing a processing instruction"));
328
35
                return false;
329
35
            }
330
3.22k
            break;
331
6.17k
        default:
332
            // If the token is none of the above, prolog processing is done.
333
6.17k
            return true;
334
23.2k
        }
335
23.2k
    }
336
337
0
    return true;
338
20.0k
}
339
340
bool QDomParser::parseBody()
341
6.17k
{
342
6.17k
    Q_ASSERT(reader);
343
344
6.17k
    std::stack<QString> tagStack;
345
715k
    while (!reader->atEnd() && !reader->hasError()) {
346
709k
        switch (reader->tokenType()) {
347
478k
        case QXmlStreamReader::StartElement:
348
478k
            tagStack.push(reader->qualifiedName().toString());
349
478k
            if (!domBuilder.startElement(reader->namespaceUri().toString(),
350
478k
                                         reader->qualifiedName().toString(),
351
478k
                                         reader->attributes())) {
352
0
                domBuilder.fatalError(
353
0
                        QDomParser::tr("Error occurred while processing a start element"));
354
0
                return false;
355
0
            }
356
478k
            break;
357
478k
        case QXmlStreamReader::EndElement:
358
64.2k
            if (tagStack.empty() || reader->qualifiedName() != tagStack.top()) {
359
0
                domBuilder.fatalError(
360
0
                        QDomParser::tr("Unexpected end element '%1'").arg(reader->name()));
361
0
                return false;
362
0
            }
363
64.2k
            tagStack.pop();
364
64.2k
            if (!domBuilder.endElement()) {
365
0
                domBuilder.fatalError(
366
0
                        QDomParser::tr("Error occurred while processing an end element"));
367
0
                return false;
368
0
            }
369
64.2k
            break;
370
153k
        case QXmlStreamReader::Characters:
371
            // Skip the content if it contains only spacing characters,
372
            // unless it's CDATA or PreserveSpacingOnlyNodes was specified.
373
153k
            if (reader->isCDATA() || domBuilder.preserveSpacingOnlyNodes()
374
153k
                || !(reader->isWhitespace() || reader->text().trimmed().isEmpty())) {
375
83.7k
                if (!domBuilder.characters(reader->text().toString(), reader->isCDATA())) {
376
34
                    domBuilder.fatalError(
377
34
                            QDomParser::tr("Error occurred while processing the element content"));
378
34
                    return false;
379
34
                }
380
83.7k
            }
381
153k
            break;
382
153k
        case QXmlStreamReader::Comment:
383
369
            if (!domBuilder.comment(reader->text().toString())) {
384
1
                domBuilder.fatalError(QDomParser::tr("Error occurred while processing comments"));
385
1
                return false;
386
1
            }
387
368
            break;
388
12.5k
        case QXmlStreamReader::ProcessingInstruction:
389
12.5k
            if (!domBuilder.processingInstruction(reader->processingInstructionTarget().toString(),
390
12.5k
                                                  reader->processingInstructionData().toString())) {
391
6
                domBuilder.fatalError(
392
6
                        QDomParser::tr("Error occurred while processing a processing instruction"));
393
6
                return false;
394
6
            }
395
12.5k
            break;
396
12.5k
        case QXmlStreamReader::EntityReference:
397
0
            if (!domBuilder.skippedEntity(reader->name().toString())) {
398
0
                domBuilder.fatalError(
399
0
                        QDomParser::tr("Error occurred while processing an entity reference"));
400
0
                return false;
401
0
            }
402
0
            break;
403
0
        default:
404
0
            domBuilder.fatalError(QDomParser::tr("Unexpected token"));
405
0
            return false;
406
709k
        }
407
408
709k
        reader->readNext();
409
709k
    }
410
411
6.13k
    if (reader->hasError()) {
412
5.56k
        domBuilder.fatalError(reader->errorString());
413
5.56k
        reader->readNext();
414
5.56k
        return false;
415
5.56k
    }
416
417
565
    if (!tagStack.empty()) {
418
0
        domBuilder.fatalError(QDomParser::tr("Tag mismatch"));
419
0
        return false;
420
0
    }
421
422
565
    return true;
423
565
}
424
425
bool QDomParser::parseMarkupDecl()
426
228
{
427
228
    Q_ASSERT(reader);
428
429
228
    const auto entities = reader->entityDeclarations();
430
228
    for (const auto &entityDecl : entities) {
431
        // Entity declarations are created only for External Entities. Internal Entities
432
        // are parsed, and QXmlStreamReader handles the parsing itself and returns the
433
        // parsed result. So we don't need to do anything for the Internal Entities.
434
0
        if (!entityDecl.publicId().isEmpty() || !entityDecl.systemId().isEmpty()) {
435
            // External Entity
436
0
            if (!domBuilder.unparsedEntityDecl(entityDecl.name().toString(),
437
0
                                               entityDecl.publicId().toString(),
438
0
                                               entityDecl.systemId().toString(),
439
0
                                               entityDecl.notationName().toString())) {
440
0
                domBuilder.fatalError(
441
0
                        QDomParser::tr("Error occurred while processing entity declaration"));
442
0
                return false;
443
0
            }
444
0
        }
445
0
    }
446
447
228
    const auto notations = reader->notationDeclarations();
448
228
    for (const auto &notationDecl : notations) {
449
0
        if (!domBuilder.notationDecl(notationDecl.name().toString(),
450
0
                                     notationDecl.publicId().toString(),
451
0
                                     notationDecl.systemId().toString())) {
452
0
            domBuilder.fatalError(
453
0
                    QDomParser::tr("Error occurred while processing notation declaration"));
454
0
            return false;
455
0
        }
456
0
    }
457
458
228
    return true;
459
228
}
460
461
QT_END_NAMESPACE
462
463
#endif // feature dom