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