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 n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
155 node->appendChild(n.get());
156
157 return true;
158}
159
160bool QDomBuilder::processingInstruction(const QString &target, const QString &data)
161{
162 QDomNodePrivate *n;
163 n = doc->createProcessingInstruction(target, data);
164 if (n) {
165 n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
166 node->appendChild(n);
167 return true;
168 } else
169 return false;
170}
171
172bool QDomBuilder::skippedEntity(const QString &name)
173{
174 QDomNodePrivate *n = doc->createEntityReference(name);
175 n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
176 node->appendChild(n);
177 return true;
178}
179
180void QDomBuilder::fatalError(const QString &message)
181{
182 parseResult.errorMessage = message;
183 parseResult.errorLine = reader->lineNumber();
184 parseResult.errorColumn = reader->columnNumber();
185}
186
187bool QDomBuilder::startEntity(const QString &name)
188{
189 entityName = name;
190 return true;
191}
192
193bool QDomBuilder::endEntity()
194{
195 entityName.clear();
196 return true;
197}
198
199bool QDomBuilder::comment(const QString &characters)
200{
201 QDomNodePrivate *n;
202 n = doc->createComment(characters);
203 n->setLocation(int(reader->lineNumber()), int(reader->columnNumber()));
204 node->appendChild(n);
205 return true;
206}
207
208bool QDomBuilder::unparsedEntityDecl(const QString &name, const QString &publicId,
209 const QString &systemId, const QString &notationName)
210{
211 QDomEntityPrivate *e =
212 new QDomEntityPrivate(doc, nullptr, name, publicId, systemId, notationName);
213 // keep the refcount balanced: appendChild() does a ref anyway.
214 e->ref.deref();
215 doc->doctype()->appendChild(e);
216 return true;
217}
218
219bool QDomBuilder::externalEntityDecl(const QString &name, const QString &publicId,
220 const QString &systemId)
221{
222 return unparsedEntityDecl(name, publicId, systemId, QString());
223}
224
225bool QDomBuilder::notationDecl(const QString &name, const QString &publicId,
226 const QString &systemId)
227{
228 QDomNotationPrivate *n = new QDomNotationPrivate(doc, nullptr, name, publicId, systemId);
229 // keep the refcount balanced: appendChild() does a ref anyway.
230 n->ref.deref();
231 doc->doctype()->appendChild(n);
232 return true;
233}
234
235/**************************************************************
236 *
237 * QDomParser
238 *
239 **************************************************************/
240
241QDomParser::QDomParser(QDomDocumentPrivate *d, QXmlStreamReader *r,
242 QDomDocument::ParseOptions options)
243 : reader(r), domBuilder(d, r, options)
244{
245}
246
247bool QDomParser::parse()
248{
249 return parseProlog() && parseBody();
250}
251
252bool QDomParser::parseProlog()
253{
254 Q_ASSERT(reader);
255
256 bool foundDtd = false;
257
258 while (!reader->atEnd()) {
259 reader->readNext();
260
261 if (reader->hasError()) {
262 domBuilder.fatalError(reader->errorString());
263 return false;
264 }
265
266 switch (reader->tokenType()) {
267 case QXmlStreamReader::StartDocument:
268 if (!reader->documentVersion().isEmpty()) {
269 QString value(u"version='"_s);
270 value += reader->documentVersion();
271 value += u'\'';
272 if (!reader->documentEncoding().isEmpty()) {
273 value += u" encoding='"_s;
274 value += reader->documentEncoding();
275 value += u'\'';
276 }
277 if (reader->isStandaloneDocument()) {
278 value += u" standalone='yes'"_s;
279 } else {
280 // Add the standalone attribute only if it was specified
281 if (reader->hasStandaloneDeclaration())
282 value += u" standalone='no'"_s;
283 }
284
285 if (!domBuilder.processingInstruction(u"xml"_s, value)) {
286 domBuilder.fatalError(
287 QDomParser::tr("Error occurred while processing XML declaration"));
288 return false;
289 }
290 }
291 break;
292 case QXmlStreamReader::DTD:
293 if (foundDtd) {
294 domBuilder.fatalError(QDomParser::tr("Multiple DTD sections are not allowed"));
295 return false;
296 }
297 foundDtd = true;
298
299 if (!domBuilder.startDTD(reader->dtdName().toString(),
300 reader->dtdPublicId().toString(),
301 reader->dtdSystemId().toString())) {
302 domBuilder.fatalError(
303 QDomParser::tr("Error occurred while processing document type declaration"));
304 return false;
305 }
306 if (!domBuilder.parseDTD(reader->text().toString()))
307 return false;
308 if (!parseMarkupDecl())
309 return false;
310 break;
311 case QXmlStreamReader::Comment:
312 if (!domBuilder.comment(reader->text().toString())) {
313 domBuilder.fatalError(QDomParser::tr("Error occurred while processing comment"));
314 return false;
315 }
316 break;
317 case QXmlStreamReader::ProcessingInstruction:
318 if (!domBuilder.processingInstruction(reader->processingInstructionTarget().toString(),
319 reader->processingInstructionData().toString())) {
320 domBuilder.fatalError(
321 QDomParser::tr("Error occurred while processing a processing instruction"));
322 return false;
323 }
324 break;
325 default:
326 // If the token is none of the above, prolog processing is done.
327 return true;
328 }
329 }
330
331 return true;
332}
333
334bool QDomParser::parseBody()
335{
336 Q_ASSERT(reader);
337
338 std::stack<QString> tagStack;
339 while (!reader->atEnd() && !reader->hasError()) {
340 switch (reader->tokenType()) {
341 case QXmlStreamReader::StartElement:
342 tagStack.push(reader->qualifiedName().toString());
343 if (!domBuilder.startElement(reader->namespaceUri().toString(),
344 reader->qualifiedName().toString(),
345 reader->attributes())) {
346 domBuilder.fatalError(
347 QDomParser::tr("Error occurred while processing a start element"));
348 return false;
349 }
350 break;
351 case QXmlStreamReader::EndElement:
352 if (tagStack.empty() || reader->qualifiedName() != tagStack.top()) {
353 domBuilder.fatalError(
354 QDomParser::tr("Unexpected end element '%1'").arg(reader->name()));
355 return false;
356 }
357 tagStack.pop();
358 if (!domBuilder.endElement()) {
359 domBuilder.fatalError(
360 QDomParser::tr("Error occurred while processing an end element"));
361 return false;
362 }
363 break;
364 case QXmlStreamReader::Characters:
365 // Skip the content if it contains only spacing characters,
366 // unless it's CDATA or PreserveSpacingOnlyNodes was specified.
367 if (reader->isCDATA() || domBuilder.preserveSpacingOnlyNodes()
368 || !(reader->isWhitespace() || reader->text().trimmed().isEmpty())) {
369 if (!domBuilder.characters(reader->text().toString(), reader->isCDATA())) {
370 domBuilder.fatalError(
371 QDomParser::tr("Error occurred while processing the element content"));
372 return false;
373 }
374 }
375 break;
376 case QXmlStreamReader::Comment:
377 if (!domBuilder.comment(reader->text().toString())) {
378 domBuilder.fatalError(QDomParser::tr("Error occurred while processing comments"));
379 return false;
380 }
381 break;
382 case QXmlStreamReader::ProcessingInstruction:
383 if (!domBuilder.processingInstruction(reader->processingInstructionTarget().toString(),
384 reader->processingInstructionData().toString())) {
385 domBuilder.fatalError(
386 QDomParser::tr("Error occurred while processing a processing instruction"));
387 return false;
388 }
389 break;
390 case QXmlStreamReader::EntityReference:
391 if (!domBuilder.skippedEntity(reader->name().toString())) {
392 domBuilder.fatalError(
393 QDomParser::tr("Error occurred while processing an entity reference"));
394 return false;
395 }
396 break;
397 default:
398 domBuilder.fatalError(QDomParser::tr("Unexpected token"));
399 return false;
400 }
401
402 reader->readNext();
403 }
404
405 if (reader->hasError()) {
406 domBuilder.fatalError(reader->errorString());
407 reader->readNext();
408 return false;
409 }
410
411 if (!tagStack.empty()) {
412 domBuilder.fatalError(QDomParser::tr("Tag mismatch"));
413 return false;
414 }
415
416 return true;
417}
418
419bool QDomParser::parseMarkupDecl()
420{
421 Q_ASSERT(reader);
422
423 const auto entities = reader->entityDeclarations();
424 for (const auto &entityDecl : entities) {
425 // Entity declarations are created only for External Entities. Internal Entities
426 // are parsed, and QXmlStreamReader handles the parsing itself and returns the
427 // parsed result. So we don't need to do anything for the Internal Entities.
428 if (!entityDecl.publicId().isEmpty() || !entityDecl.systemId().isEmpty()) {
429 // External Entity
430 if (!domBuilder.unparsedEntityDecl(entityDecl.name().toString(),
431 entityDecl.publicId().toString(),
432 entityDecl.systemId().toString(),
433 entityDecl.notationName().toString())) {
434 domBuilder.fatalError(
435 QDomParser::tr("Error occurred while processing entity declaration"));
436 return false;
437 }
438 }
439 }
440
441 const auto notations = reader->notationDeclarations();
442 for (const auto &notationDecl : notations) {
443 if (!domBuilder.notationDecl(notationDecl.name().toString(),
444 notationDecl.publicId().toString(),
445 notationDecl.systemId().toString())) {
446 domBuilder.fatalError(
447 QDomParser::tr("Error occurred while processing notation declaration"));
448 return false;
449 }
450 }
451
452 return true;
453}
454
455QT_END_NAMESPACE
456
457#endif // feature dom