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