1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QDomDocument> |
31 | #include <qthread.h> |
32 | #include <qtcpserver.h> |
33 | #include <qtcpsocket.h> |
34 | #include <QtTest/QtTest> |
35 | #include <QtCore/qatomic.h> |
36 | #include <QtCore/qsemaphore.h> |
37 | #include <qfile.h> |
38 | #include <qstring.h> |
39 | #include <qdir.h> |
40 | #include <qbuffer.h> |
41 | #include "parser/parser.h" |
42 | |
43 | static const char *const inputString = "<!DOCTYPE inferno [<!ELEMENT inferno (circle+)><!ELEMENT circle (#PCDATA)>]><inferno><circle /><circle /></inferno>" ; |
44 | static const char *const refString = "setDocumentLocator(locator={columnNumber=1, lineNumber=1})\nstartDocument()\nstartDTD(name=\"inferno\", publicId=\"\", systemId=\"\")\nendDTD()\nstartElement(namespaceURI=\"\", localName=\"inferno\", qName=\"inferno\", atts=[])\nstartElement(namespaceURI=\"\", localName=\"circle\", qName=\"circle\", atts=[])\nendElement(namespaceURI=\"\", localName=\"circle\", qName=\"circle\")\nstartElement(namespaceURI=\"\", localName=\"circle\", qName=\"circle\", atts=[])\nendElement(namespaceURI=\"\", localName=\"circle\", qName=\"circle\")\nendElement(namespaceURI=\"\", localName=\"inferno\", qName=\"inferno\")\nendDocument()\n" ; |
45 | |
46 | #define TEST_PORT 1088 |
47 | |
48 | class XmlServer : public QThread |
49 | { |
50 | Q_OBJECT |
51 | public: |
52 | XmlServer(QObject *parent = 0) : QThread(parent) {} |
53 | |
54 | QSemaphore threadStarted; |
55 | bool listening = false; |
56 | QAtomicInt quitSoon; |
57 | |
58 | protected: |
59 | virtual void run(); |
60 | }; |
61 | |
62 | #define CHUNK_SIZE 2048 |
63 | |
64 | void XmlServer::run() |
65 | { |
66 | QTcpServer srv; |
67 | |
68 | listening = srv.listen(address: QHostAddress::Any, TEST_PORT); |
69 | threadStarted.release(); |
70 | |
71 | if (!listening) { |
72 | qWarning() << "Failed to listen on" << TEST_PORT << srv.errorString(); |
73 | return; |
74 | } |
75 | |
76 | for (;;) { |
77 | srv.waitForNewConnection(msec: 100); |
78 | |
79 | if (QTcpSocket *sock = srv.nextPendingConnection()) { |
80 | QByteArray fileName; |
81 | for (;;) { |
82 | char c; |
83 | if (sock->getChar(c: &c)) { |
84 | if (c == '\n') |
85 | break; |
86 | fileName.append(c); |
87 | } else { |
88 | if (!sock->waitForReadyRead(msecs: -1)) |
89 | break; |
90 | } |
91 | } |
92 | |
93 | QFile file(QString::fromLocal8Bit(str: fileName)); |
94 | if (!file.open(flags: QIODevice::ReadOnly)) { |
95 | qWarning() << "XmlServer::run(): could not open" << fileName; |
96 | sock->abort(); |
97 | delete sock; |
98 | continue; |
99 | } |
100 | |
101 | QByteArray data = file.readAll(); |
102 | for (int i = 0; i < data.size();) { |
103 | int cnt = qMin(CHUNK_SIZE, b: data.size() - i); |
104 | sock->write(data: data.constData() + i, len: cnt); |
105 | i += cnt; |
106 | sock->flush(); |
107 | QTest::qSleep(ms: 1); |
108 | |
109 | if (quitSoon.loadAcquire()) { |
110 | sock->abort(); |
111 | break; |
112 | } |
113 | } |
114 | |
115 | sock->disconnectFromHost(); |
116 | delete sock; |
117 | } |
118 | |
119 | if (quitSoon.loadAcquire()) |
120 | break; |
121 | } |
122 | |
123 | srv.close(); |
124 | } |
125 | |
126 | class tst_QXmlSimpleReader : public QObject |
127 | { |
128 | Q_OBJECT |
129 | |
130 | #if QT_DEPRECATED_SINCE(5, 15) |
131 | public: |
132 | tst_QXmlSimpleReader(); |
133 | ~tst_QXmlSimpleReader(); |
134 | |
135 | private slots: |
136 | void initTestCase(); |
137 | void testGoodXmlFile(); |
138 | void testGoodXmlFile_data(); |
139 | void testBadXmlFile(); |
140 | void testBadXmlFile_data(); |
141 | void testIncrementalParsing(); |
142 | void testIncrementalParsing_data(); |
143 | void setDataQString(); |
144 | void inputFromQIODevice(); |
145 | void inputFromString(); |
146 | void inputFromSocket_data(); |
147 | void inputFromSocket(); |
148 | |
149 | void idsInParseException1(); |
150 | void idsInParseException2(); |
151 | void preserveCharacterReferences() const; |
152 | void reportNamespace() const; |
153 | void reportNamespace_data() const; |
154 | void roundtripWithNamespaces() const; |
155 | void dtdRecursionLimit(); |
156 | |
157 | private: |
158 | static QDomDocument fromByteArray(const QString &title, const QByteArray &ba, bool *ok); |
159 | XmlServer *server; |
160 | QString prefix; |
161 | #endif // QT_DEPRECATED_SINCE(5, 15) |
162 | }; |
163 | |
164 | #if QT_DEPRECATED_SINCE(5, 15) |
165 | QT_WARNING_PUSH |
166 | QT_WARNING_DISABLE_DEPRECATED |
167 | |
168 | tst_QXmlSimpleReader::tst_QXmlSimpleReader() : server(new XmlServer(this)) |
169 | { |
170 | server->start(); |
171 | } |
172 | |
173 | tst_QXmlSimpleReader::~tst_QXmlSimpleReader() |
174 | { |
175 | server->quitSoon.storeRelease(newValue: 1); |
176 | server->wait(); |
177 | } |
178 | |
179 | class MyErrorHandler : public QXmlErrorHandler |
180 | { |
181 | public: |
182 | QString publicId; |
183 | QString systemId; |
184 | |
185 | virtual bool error(const QXmlParseException &) |
186 | { |
187 | return false; |
188 | } |
189 | |
190 | virtual QString errorString() const |
191 | { |
192 | return QString(); |
193 | } |
194 | |
195 | virtual bool fatalError(const QXmlParseException &exception) |
196 | { |
197 | publicId = exception.publicId(); |
198 | systemId = exception.systemId(); |
199 | return true; |
200 | } |
201 | |
202 | virtual bool warning(const QXmlParseException &) |
203 | { |
204 | return true; |
205 | } |
206 | |
207 | }; |
208 | |
209 | void tst_QXmlSimpleReader::initTestCase() |
210 | { |
211 | prefix = QFileInfo(QFINDTESTDATA("xmldocs" )).absolutePath(); |
212 | if (prefix.isEmpty()) |
213 | QFAIL("Cannot find xmldocs testdata!" ); |
214 | QDir::setCurrent(prefix); |
215 | } |
216 | |
217 | void tst_QXmlSimpleReader::idsInParseException1() |
218 | { |
219 | MyErrorHandler handler; |
220 | QXmlSimpleReader reader; |
221 | |
222 | reader.setErrorHandler(&handler); |
223 | |
224 | /* A non-wellformed XML document with PUBLIC and SYSTEM. */ |
225 | QByteArray input("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " |
226 | "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" |
227 | "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">" |
228 | "<head>" |
229 | "<a/><a/><a/>" |
230 | "<head/>" ); |
231 | |
232 | QBuffer buff(&input); |
233 | QXmlInputSource source(&buff); |
234 | |
235 | /* Yes, parsing should be reported as a failure. */ |
236 | QVERIFY(!reader.parse(source)); |
237 | |
238 | QCOMPARE(handler.publicId, QString::fromLatin1("-//W3C//DTD XHTML 1.0 Strict//EN" )); |
239 | QCOMPARE(handler.systemId, QString::fromLatin1("http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" )); |
240 | } |
241 | |
242 | void tst_QXmlSimpleReader::idsInParseException2() |
243 | { |
244 | MyErrorHandler handler; |
245 | QXmlSimpleReader reader; |
246 | |
247 | reader.setErrorHandler(&handler); |
248 | |
249 | /* A non-wellformed XML document with only SYSTEM. */ |
250 | QByteArray input("<!DOCTYPE html SYSTEM \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" |
251 | "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">" |
252 | "<head>" |
253 | "<a/><a/><a/>" |
254 | "<head/>" ); |
255 | |
256 | QBuffer buff(&input); |
257 | QXmlInputSource source(&buff); |
258 | |
259 | /* Yes, parsing should be reported as a failure. */ |
260 | QVERIFY(!reader.parse(source)); |
261 | |
262 | QCOMPARE(handler.publicId, QString()); |
263 | QCOMPARE(handler.systemId, QString::fromLatin1("http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" )); |
264 | } |
265 | |
266 | static QStringList findXmlFiles(QString dir_name) |
267 | { |
268 | QStringList result; |
269 | |
270 | dir_name = QFINDTESTDATA(dir_name); |
271 | QDir dir(dir_name); |
272 | QFileInfoList file_list = dir.entryInfoList(nameFilters: QStringList("*.xml" ), filters: QDir::Files, sort: QDir::Name); |
273 | |
274 | QFileInfoList::const_iterator it = file_list.begin(); |
275 | for (; it != file_list.end(); ++it) { |
276 | const QFileInfo &file_info = *it; |
277 | result.append(t: file_info.filePath()); |
278 | } |
279 | |
280 | return result; |
281 | } |
282 | |
283 | |
284 | void tst_QXmlSimpleReader::testGoodXmlFile_data() |
285 | { |
286 | const char * const good_data_dirs[] = { |
287 | "xmldocs/valid/sa" , |
288 | "xmldocs/valid/not-sa" , |
289 | "xmldocs/valid/ext-sa" , |
290 | 0 |
291 | }; |
292 | const char * const *d = good_data_dirs; |
293 | |
294 | QStringList good_file_list; |
295 | for (; *d != 0; ++d) |
296 | good_file_list += findXmlFiles(dir_name: *d); |
297 | |
298 | QTest::addColumn<QString>(name: "file_name" ); |
299 | QStringList::const_iterator it = good_file_list.begin(); |
300 | for (; it != good_file_list.end(); ++it) |
301 | QTest::newRow(dataTag: (*it).toLatin1()) << *it; |
302 | } |
303 | |
304 | void tst_QXmlSimpleReader::testGoodXmlFile() |
305 | { |
306 | QFETCH(QString, file_name); |
307 | QFile file(file_name); |
308 | QVERIFY(file.open(QIODevice::ReadOnly)); |
309 | QString content = file.readAll(); |
310 | file.close(); |
311 | QVERIFY(file.open(QIODevice::ReadOnly)); |
312 | Parser parser; |
313 | |
314 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/valid/sa/089.xml" ).toLocal8Bit().constData(), "a form feed character is not accepted in XML" , Continue); |
315 | QVERIFY(parser.parseFile(&file)); |
316 | |
317 | QFile ref_file(file_name + ".ref" ); |
318 | QVERIFY(ref_file.open(QIODevice::ReadOnly | QIODevice::Text)); |
319 | QTextStream ref_stream(&ref_file); |
320 | ref_stream.setCodec("UTF-8" ); |
321 | QString ref_file_contents = ref_stream.readAll(); |
322 | |
323 | QCOMPARE(parser.result(), ref_file_contents); |
324 | } |
325 | |
326 | void tst_QXmlSimpleReader::testBadXmlFile_data() |
327 | { |
328 | const char * const bad_data_dirs[] = { |
329 | "xmldocs/not-wf/sa" , |
330 | 0 |
331 | }; |
332 | const char * const *d = bad_data_dirs; |
333 | |
334 | QStringList bad_file_list; |
335 | for (; *d != 0; ++d) |
336 | bad_file_list += findXmlFiles(dir_name: *d); |
337 | |
338 | QTest::addColumn<QString>(name: "file_name" ); |
339 | QStringList::const_iterator it = bad_file_list.begin(); |
340 | for (; it != bad_file_list.end(); ++it) |
341 | QTest::newRow(dataTag: (*it).toLatin1()) << *it; |
342 | } |
343 | |
344 | void tst_QXmlSimpleReader::testBadXmlFile() |
345 | { |
346 | QFETCH(QString, file_name); |
347 | QFile file(file_name); |
348 | QVERIFY(file.open(QIODevice::ReadOnly)); |
349 | Parser parser; |
350 | |
351 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/030.xml" ).toLocal8Bit().constData(), "a form feed character is not accepted in XML" , Continue); |
352 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/031.xml" ).toLocal8Bit().constData(), "a form feed character is not accepted in a processing instruction" , Continue); |
353 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/032.xml" ).toLocal8Bit().constData(), "a form feed character is not accepted in a comment" , Continue); |
354 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/033.xml" ).toLocal8Bit().constData(), "overlong sequence - small latin letter d should be rejected" , Continue); |
355 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/038.xml" ).toLocal8Bit().constData(), "attribute x redefined; should be rejected" , Continue); |
356 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/072.xml" ).toLocal8Bit().constData(), "entity foo not defined" , Continue); |
357 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/073.xml" ).toLocal8Bit().constData(), "entity f not defined" , Continue); |
358 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/074.xml" ).toLocal8Bit().constData(), "entity e is not well-formed (</foo><foo>)" , Continue); |
359 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/076.xml" ).toLocal8Bit().constData(), "entity foo is not defined" , Continue); |
360 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/077.xml" ).toLocal8Bit().constData(), "entity bar is not defined within the definition of entity foo" , Continue); |
361 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/078.xml" ).toLocal8Bit().constData(), "entity foo not defined" , Continue); |
362 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/085.xml" ).toLocal8Bit().constData(), "Unfinished Public or System Id" , Continue); |
363 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/086.xml" ).toLocal8Bit().constData(), "Unfinished Public or System Id" , Continue); |
364 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/087.xml" ).toLocal8Bit().constData(), "Unfinished Public or System Id" , Continue); |
365 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/101.xml" ).toLocal8Bit().constData(), "Invalid XML encoding name (space before utf-8)" , Continue); |
366 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/102.xml" ).toLocal8Bit().constData(), "Invalid version specification (1.0 followed by space)" , Continue); |
367 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/104.xml" ).toLocal8Bit().constData(), "Premature end of data in tag foo" , Continue); |
368 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/116.xml" ).toLocal8Bit().constData(), "Invalid decimal value" , Continue); |
369 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/117.xml" ).toLocal8Bit().constData(), "No name" , Continue); |
370 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/119.xml" ).toLocal8Bit().constData(), "No name" , Continue); |
371 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/122.xml" ).toLocal8Bit().constData(), "; expected in declaration of element" , Continue); |
372 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/132.xml" ).toLocal8Bit().constData(), "; expected in declaration of element" , Continue); |
373 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/142.xml" ).toLocal8Bit().constData(), "Invalid value '0'" , Continue); |
374 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/143.xml" ).toLocal8Bit().constData(), "Invalid value '31'" , Continue); |
375 | |
376 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/144.xml" ).toLocal8Bit().constData(), "noncharacter code 0xFFFF should be rejected" , Continue); |
377 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/145.xml" ).toLocal8Bit().constData(), "surrogate code point 0xD800 should be rejected" , Continue); |
378 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/146.xml" ).toLocal8Bit().constData(), "code point out-of-range 0x110000 (must be < 0x10FFFE)" , Abort); |
379 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/160.xml" ).toLocal8Bit().constData(), "Parameter references forbidden in internal subset" , Continue); |
380 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/162.xml" ).toLocal8Bit().constData(), "Parameter references forbidden in internal subset" , Continue); |
381 | |
382 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/168.xml" ).toLocal8Bit().constData(), "Surrogate code point 0xEDA080 should be rejected" , Continue); |
383 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/169.xml" ).toLocal8Bit().constData(), "Surrogate code point 0xEDB080 should be rejected" , Continue); |
384 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/170.xml" ).toLocal8Bit().constData(), "Code point 0xF7808080 should be rejected" , Continue); |
385 | |
386 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/180.xml" ).toLocal8Bit().constData(), "Entity e is not defined" , Continue); |
387 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/181.xml" ).toLocal8Bit().constData(), "Unregistered error message" , Continue); |
388 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/182.xml" ).toLocal8Bit().constData(), "Comment not terminated" , Continue); |
389 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/185.xml" ).toLocal8Bit().constData(), "Entity e not defined" , Continue); |
390 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/186.xml" ).toLocal8Bit().constData(), "Attributes constructs error" , Continue); |
391 | |
392 | QVERIFY(!parser.parseFile(&file)); |
393 | |
394 | QFile ref_file(file_name + ".ref" ); |
395 | QVERIFY(ref_file.open(QIODevice::ReadOnly | QIODevice::Text)); |
396 | QTextStream ref_stream(&ref_file); |
397 | ref_stream.setCodec("UTF-8" ); |
398 | QString ref_file_contents = ref_stream.readAll(); |
399 | |
400 | QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/145.xml" ).toLocal8Bit().constData(), "Surrogate code point 0xD800 should be rejected" , Continue); |
401 | |
402 | QCOMPARE(parser.result(), ref_file_contents); |
403 | } |
404 | |
405 | void tst_QXmlSimpleReader::testIncrementalParsing_data() |
406 | { |
407 | QTest::addColumn<QString>(name: "file_name" ); |
408 | QTest::addColumn<int>(name: "chunkSize" ); |
409 | |
410 | const char * const good_data_dirs[] = { |
411 | "xmldocs/valid/sa" , |
412 | "xmldocs/valid/not-sa" , |
413 | "xmldocs/valid/ext-sa" , |
414 | 0 |
415 | }; |
416 | const char * const *d = good_data_dirs; |
417 | |
418 | QStringList good_file_list; |
419 | for (; *d != 0; ++d) |
420 | good_file_list += findXmlFiles(dir_name: *d); |
421 | |
422 | for (int i=1; i<10; ++i) { |
423 | QStringList::const_iterator it = good_file_list.begin(); |
424 | const QString skip49 = QFINDTESTDATA("xmldocs/valid/sa/049.xml" ); |
425 | const QString skip50 = QFINDTESTDATA("xmldocs/valid/sa/050.xml" ); |
426 | const QString skip51 = QFINDTESTDATA("xmldocs/valid/sa/051.xml" ); |
427 | const QString skip52 = QFINDTESTDATA("xmldocs/valid/sa/052.xml" ); |
428 | const QString skip89 = QFINDTESTDATA("xmldocs/valid/sa/089.xml" ); |
429 | |
430 | for (; it != good_file_list.end(); ++it) { |
431 | if ( *it == skip89 ) |
432 | continue;// TODO: fails at the moment -- don't bother |
433 | if ( i==1 && ( |
434 | *it == skip49 || |
435 | *it == skip50 || |
436 | *it == skip51 || |
437 | *it == skip52 ) ) { |
438 | continue; // TODO: fails at the moment -- don't bother |
439 | } |
440 | QTest::newRow(dataTag: QString("%1 %2" ).arg(a: *it).arg(a: i).toLatin1()) << *it << i; |
441 | } |
442 | } |
443 | } |
444 | |
445 | void tst_QXmlSimpleReader::testIncrementalParsing() |
446 | { |
447 | QFETCH(QString, file_name); |
448 | QFETCH(int, chunkSize); |
449 | |
450 | QFile file(file_name); |
451 | QVERIFY(file.open(QIODevice::ReadOnly)); |
452 | |
453 | Parser parser; |
454 | QXmlInputSource source; |
455 | bool first = true; |
456 | while (!file.atEnd()) { |
457 | source.setData(file.read(maxlen: chunkSize)); |
458 | if(first) { |
459 | QVERIFY(parser.parse(&source, true)); |
460 | first = false; |
461 | } else { |
462 | QVERIFY(parser.parseContinue()); |
463 | } |
464 | } |
465 | // detect end of document |
466 | QVERIFY(parser.parseContinue()); |
467 | // parsing should fail after the end of the document was reached |
468 | QVERIFY(!parser.parseContinue()); |
469 | |
470 | QFile ref_file(file_name + ".ref" ); |
471 | QVERIFY(ref_file.open(QIODevice::ReadOnly | QIODevice::Text)); |
472 | QTextStream ref_stream(&ref_file); |
473 | ref_stream.setCodec("UTF-8" ); |
474 | QString ref_file_contents = ref_stream.readAll(); |
475 | |
476 | QCOMPARE(parser.result(), ref_file_contents); |
477 | } |
478 | |
479 | void tst_QXmlSimpleReader::setDataQString() |
480 | { |
481 | QString input = inputString; |
482 | QString ref = refString; |
483 | |
484 | QXmlInputSource source; |
485 | Parser parser; |
486 | |
487 | source.setData(input); |
488 | QVERIFY(parser.parse(&source,false)); |
489 | |
490 | QBuffer resultBuffer; |
491 | resultBuffer.setData(parser.result().toLatin1()); |
492 | |
493 | QBuffer refBuffer; |
494 | refBuffer.setData(ref.toLatin1()); |
495 | |
496 | resultBuffer.open(openMode: QIODevice::ReadOnly); |
497 | refBuffer.open(openMode: QIODevice::ReadOnly); |
498 | |
499 | bool success = true; |
500 | while (resultBuffer.canReadLine()) { |
501 | if (!refBuffer.canReadLine()) { |
502 | success = false; break ; |
503 | } |
504 | if (resultBuffer.readLine().simplified() != refBuffer.readLine().simplified()) { |
505 | success = false; break ; |
506 | } |
507 | } |
508 | QVERIFY(success); |
509 | } |
510 | |
511 | void tst_QXmlSimpleReader::inputFromQIODevice() |
512 | { |
513 | QBuffer inputBuffer; |
514 | inputBuffer.setData(inputString); |
515 | |
516 | QXmlInputSource source(&inputBuffer); |
517 | Parser parser; |
518 | |
519 | QVERIFY(parser.parse(&source,false)); |
520 | |
521 | QBuffer resultBuffer; |
522 | resultBuffer.setData(parser.result().toLatin1()); |
523 | |
524 | QBuffer refBuffer; |
525 | refBuffer.setData(refString); |
526 | |
527 | resultBuffer.open(openMode: QIODevice::ReadOnly); |
528 | refBuffer.open(openMode: QIODevice::ReadOnly); |
529 | |
530 | bool success = true; |
531 | while (resultBuffer.canReadLine()) { |
532 | if (!refBuffer.canReadLine()) { |
533 | success = false; break ; |
534 | } |
535 | if (resultBuffer.readLine().simplified() != refBuffer.readLine().simplified()) { |
536 | success = false; break ; |
537 | } |
538 | } |
539 | QVERIFY(success); |
540 | } |
541 | |
542 | void tst_QXmlSimpleReader::inputFromString() |
543 | { |
544 | QString str = "<foo><bar>kake</bar><bar>ja</bar></foo>" ; |
545 | QBuffer buff; |
546 | buff.setData(adata: (char*)str.utf16(), alen: str.size()*sizeof(ushort)); |
547 | |
548 | QXmlInputSource input(&buff); |
549 | |
550 | QXmlSimpleReader reader; |
551 | QXmlDefaultHandler handler; |
552 | reader.setContentHandler(&handler); |
553 | |
554 | QVERIFY(reader.parse(&input)); |
555 | } |
556 | |
557 | void tst_QXmlSimpleReader::inputFromSocket_data() |
558 | { |
559 | QStringList files = findXmlFiles(dir_name: QLatin1String("encodings" )); |
560 | QVERIFY(files.count() > 0); |
561 | |
562 | QTest::addColumn<QString>(name: "file_name" ); |
563 | |
564 | foreach (const QString &file_name, files) |
565 | QTest::newRow(dataTag: file_name.toLatin1()) << file_name; |
566 | } |
567 | |
568 | void tst_QXmlSimpleReader::inputFromSocket() |
569 | { |
570 | QFETCH(QString, file_name); |
571 | #ifdef Q_OS_WINRT |
572 | QSKIP("WinRT does not support connecting to localhost" ); |
573 | #endif |
574 | |
575 | if (!server->threadStarted.tryAcquire(n: 1, timeout: 15000)) { |
576 | // If something is wrong with QThreads, it's not a reason to fail |
577 | // XML-test, we are not testing QThread here after all! |
578 | QSKIP("XmlServer/thread has not started yet" ); |
579 | } |
580 | |
581 | // Subsequent runs should be able to acquire the semaphore. |
582 | server->threadStarted.release(n: 1); |
583 | |
584 | if (!server->listening) { |
585 | // Again, QTcpServer is not the subject of this test! |
586 | QSKIP("QTcpServer::listen failed, bailing out" ); |
587 | } |
588 | |
589 | QTcpSocket sock; |
590 | sock.connectToHost(address: QHostAddress::LocalHost, TEST_PORT); |
591 | QVERIFY2(sock.waitForConnected(), |
592 | qPrintable(QStringLiteral("Cannot connect on port " ) + QString::number(TEST_PORT) |
593 | + QStringLiteral(": " ) + sock.errorString())); |
594 | |
595 | sock.write(data: file_name.toLocal8Bit() + "\n" ); |
596 | QVERIFY(sock.waitForBytesWritten()); |
597 | |
598 | QXmlInputSource input(&sock); |
599 | |
600 | QXmlSimpleReader reader; |
601 | QXmlDefaultHandler handler; |
602 | reader.setContentHandler(&handler); |
603 | |
604 | QVERIFY(reader.parse(&input)); |
605 | |
606 | // qDebug() << "tst_QXmlSimpleReader::inputFromSocket(): success" << file_name; |
607 | } |
608 | |
609 | void tst_QXmlSimpleReader::preserveCharacterReferences() const |
610 | { |
611 | class Handler : public QXmlDefaultHandler |
612 | { |
613 | public: |
614 | virtual bool characters(const QString &chars) |
615 | { |
616 | received = chars; |
617 | return true; |
618 | } |
619 | |
620 | QString received; |
621 | }; |
622 | |
623 | { |
624 | QByteArray input("<e>A    A</e>" ); |
625 | |
626 | QBuffer buff(&input); |
627 | QXmlInputSource source(&buff); |
628 | |
629 | Handler h; |
630 | QXmlSimpleReader reader; |
631 | reader.setContentHandler(&h); |
632 | QVERIFY(reader.parse(&source, false)); |
633 | |
634 | QCOMPARE(h.received, QLatin1Char('A') + QString(4, QChar(160)) + QLatin1Char('A')); |
635 | } |
636 | |
637 | { |
638 | QByteArray input("<e>    </e>" ); |
639 | |
640 | QBuffer buff(&input); |
641 | QXmlInputSource source(&buff); |
642 | |
643 | Handler h; |
644 | QXmlSimpleReader reader; |
645 | reader.setContentHandler(&h); |
646 | QVERIFY(reader.parse(&source, false)); |
647 | |
648 | QCOMPARE(h.received, QString(4, QChar(160))); |
649 | } |
650 | } |
651 | |
652 | void tst_QXmlSimpleReader::reportNamespace() const |
653 | { |
654 | class Handler : public QXmlDefaultHandler |
655 | { |
656 | public: |
657 | virtual bool startElement(const QString &namespaceURI, |
658 | const QString &localName, |
659 | const QString &qName, |
660 | const QXmlAttributes &) |
661 | { |
662 | startNamespaceURI = namespaceURI; |
663 | startLocalName = localName; |
664 | startQName = qName; |
665 | |
666 | return true; |
667 | } |
668 | |
669 | virtual bool endElement(const QString &namespaceURI, |
670 | const QString &localName, |
671 | const QString &qName) |
672 | { |
673 | endNamespaceURI = namespaceURI; |
674 | endLocalName = localName; |
675 | endQName = qName; |
676 | |
677 | return true; |
678 | } |
679 | |
680 | QString startLocalName; |
681 | QString startQName; |
682 | QString startNamespaceURI; |
683 | QString endLocalName; |
684 | QString endQName; |
685 | QString endNamespaceURI; |
686 | }; |
687 | |
688 | QXmlSimpleReader reader; |
689 | Handler handler; |
690 | reader.setContentHandler(&handler); |
691 | |
692 | QFETCH(QByteArray, input); |
693 | |
694 | QBuffer buffer(&input); |
695 | QVERIFY(buffer.open(QIODevice::ReadOnly)); |
696 | |
697 | QXmlInputSource source(&buffer); |
698 | QVERIFY(reader.parse(source)); |
699 | |
700 | QFETCH(QString, expectedQName); |
701 | QFETCH(QString, expectedLocalName); |
702 | QFETCH(QString, expectedNamespace); |
703 | |
704 | QCOMPARE(handler.startNamespaceURI, expectedNamespace); |
705 | QCOMPARE(handler.startLocalName, expectedLocalName); |
706 | QCOMPARE(handler.startQName, expectedQName); |
707 | |
708 | QCOMPARE(handler.endNamespaceURI, expectedNamespace); |
709 | QCOMPARE(handler.endLocalName, expectedLocalName); |
710 | QCOMPARE(handler.endQName, expectedQName); |
711 | } |
712 | |
713 | void tst_QXmlSimpleReader::reportNamespace_data() const |
714 | { |
715 | QTest::addColumn<QByteArray>(name: "input" ); |
716 | QTest::addColumn<QString>(name: "expectedQName" ); |
717 | QTest::addColumn<QString>(name: "expectedLocalName" ); |
718 | QTest::addColumn<QString>(name: "expectedNamespace" ); |
719 | |
720 | QTest::newRow(dataTag: "default ns" ) << QByteArray("<element xmlns='http://example.com/'/>" ) |
721 | << QString("element" ) |
722 | << QString("element" ) |
723 | << QString("http://example.com/" ); |
724 | |
725 | QTest::newRow(dataTag: "with prefix" ) << QByteArray("<p:element xmlns:p='http://example.com/'/>" ) |
726 | << QString("p:element" ) |
727 | << QString("element" ) |
728 | << QString("http://example.com/" ); |
729 | } |
730 | |
731 | QDomDocument tst_QXmlSimpleReader::fromByteArray(const QString &title, const QByteArray &ba, bool *ok) |
732 | { |
733 | QDomDocument doc(title); |
734 | *ok = doc.setContent(text: ba, namespaceProcessing: true); |
735 | return doc; |
736 | } |
737 | |
738 | void tst_QXmlSimpleReader::roundtripWithNamespaces() const |
739 | { |
740 | const char *const expected = "<element b:attr=\"value\" xmlns:a=\"http://www.example.com/A\" xmlns:b=\"http://www.example.com/B\" />\n" ; |
741 | bool ok; |
742 | |
743 | { |
744 | const char *const xml = "<element xmlns:b=\"http://www.example.com/B\" b:attr=\"value\" xmlns:a=\"http://www.example.com/A\"/>" ; |
745 | |
746 | const QDomDocument one(fromByteArray(title: "document" , ba: xml, ok: &ok)); |
747 | QVERIFY(ok); |
748 | const QDomDocument two(fromByteArray(title: "document2" , ba: one.toByteArray(2), ok: &ok)); |
749 | QVERIFY(ok); |
750 | |
751 | QEXPECT_FAIL("" , "Known problem, see 154573. The fix happens to break uic." , Abort); |
752 | |
753 | QCOMPARE(expected, one.toByteArray().constData()); |
754 | QCOMPARE(one.toByteArray(2).constData(), two.toByteArray(2).constData()); |
755 | QCOMPARE(two.toByteArray(2).constData(), two.toByteArray(2).constData()); |
756 | } |
757 | |
758 | { |
759 | const char *const xml = "<element b:attr=\"value\" xmlns:b=\"http://www.example.com/B\" xmlns:a=\"http://www.example.com/A\"/>" ; |
760 | |
761 | const QDomDocument one(fromByteArray(title: "document" , ba: xml, ok: &ok)); |
762 | QVERIFY(ok); |
763 | const QDomDocument two(fromByteArray(title: "document2" , ba: one.toByteArray(2), ok: &ok)); |
764 | QVERIFY(ok); |
765 | |
766 | QCOMPARE(expected, one.toByteArray().constData()); |
767 | QCOMPARE(one.toByteArray(2).constData(), two.toByteArray(2).constData()); |
768 | QCOMPARE(two.toByteArray(2).constData(), two.toByteArray(2).constData()); |
769 | } |
770 | } |
771 | |
772 | class TestHandler : public QXmlDefaultHandler |
773 | { |
774 | public: |
775 | TestHandler() : |
776 | recursionCount(0) |
777 | { |
778 | } |
779 | |
780 | bool internalEntityDecl(const QString &name, const QString &value) |
781 | { |
782 | ++recursionCount; |
783 | return QXmlDefaultHandler::internalEntityDecl(name, value); |
784 | } |
785 | |
786 | int recursionCount; |
787 | }; |
788 | |
789 | void tst_QXmlSimpleReader::dtdRecursionLimit() |
790 | { |
791 | QFile file(QFINDTESTDATA("xmldocs/2-levels-nested-dtd.xml" )); |
792 | QVERIFY(file.open(QIODevice::ReadOnly)); |
793 | QXmlSimpleReader xmlReader; |
794 | { |
795 | QXmlInputSource source(&file); |
796 | TestHandler handler; |
797 | xmlReader.setDeclHandler(&handler); |
798 | xmlReader.setErrorHandler(&handler); |
799 | QVERIFY(!xmlReader.parse(&source)); |
800 | } |
801 | |
802 | file.close(); |
803 | file.setFileName(QFINDTESTDATA("xmldocs/1-levels-nested-dtd.xml" )); |
804 | QVERIFY(file.open(QIODevice::ReadOnly)); |
805 | { |
806 | QXmlInputSource source(&file); |
807 | TestHandler handler; |
808 | xmlReader.setDeclHandler(&handler); |
809 | xmlReader.setErrorHandler(&handler); |
810 | QVERIFY(!xmlReader.parse(&source)); |
811 | // The error wasn't because of the recursion limit being reached, |
812 | // it was because the document is not valid. |
813 | QVERIFY(handler.recursionCount < 2); |
814 | } |
815 | |
816 | file.close(); |
817 | file.setFileName(QFINDTESTDATA("xmldocs/internal-entity-polynomial-attribute.xml" )); |
818 | QVERIFY(file.open(QIODevice::ReadOnly)); |
819 | { |
820 | QXmlInputSource source(&file); |
821 | TestHandler handler; |
822 | xmlReader.setDeclHandler(&handler); |
823 | xmlReader.setErrorHandler(&handler); |
824 | QVERIFY(!xmlReader.parse(&source)); |
825 | QCOMPARE(handler.recursionCount, 2); |
826 | } |
827 | } |
828 | |
829 | QT_WARNING_POP |
830 | #endif // QT_DEPRECATED_SINCE(5, 15) |
831 | |
832 | QTEST_MAIN(tst_QXmlSimpleReader) |
833 | #include "tst_qxmlsimplereader.moc" |
834 | |