/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 ¬ationName) |
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 ¬ationDecl : 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 |