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#include <qtest.h>
30#include <QQmlEngine>
31#include <QQmlComponent>
32#include <QDebug>
33#include <QScopedPointer>
34#include <QNetworkCookieJar>
35#include <QThread>
36#include <QMutex>
37#include <QWaitCondition>
38#include <QTemporaryFile>
39
40#if QT_CONFIG(process)
41#include <QProcess>
42#include <QProcessEnvironment>
43#endif
44
45#include "testhttpserver.h"
46#include "../../shared/util.h"
47
48class tst_qqmlxmlhttprequest : public QQmlDataTest
49{
50 Q_OBJECT
51public:
52 tst_qqmlxmlhttprequest() {}
53
54private slots:
55 void initTestCase();
56
57 void domExceptionCodes();
58 void callbackException();
59 void callbackException_data();
60 void staticStateValues();
61 void instanceStateValues();
62 void constructor();
63 void defaultState();
64 void open();
65 void open_data();
66 void open_invalid_method();
67 void open_sync();
68 void open_arg_count();
69 void setRequestHeader();
70 void setRequestHeader_caseInsensitive();
71 void setRequestHeader_unsent();
72 void setRequestHeader_illegalName_data();
73 void setRequestHeader_illegalName();
74 void setRequestHeader_sent();
75 void setRequestHeader_args();
76 void send_unsent();
77 void send_alreadySent();
78 void send_ignoreData();
79 void send_withdata();
80 void send_withdata_data();
81 void send_options();
82 void send_options_data();
83 void send_patch();
84 void abort();
85 void abort_unsent();
86 void abort_opened();
87 void getResponseHeader();
88 void getResponseHeader_unsent();
89 void getResponseHeader_sent();
90 void getResponseHeader_args();
91 void getAllResponseHeaders();
92 void getAllResponseHeaders_unsent();
93 void getAllResponseHeaders_sent();
94 void getAllResponseHeaders_args();
95 void getBinaryData();
96 void getJsonData();
97 void status();
98 void status_data();
99 void statusText();
100 void statusText_data();
101 void responseText();
102 void responseText_data();
103 void responseXML_invalid();
104 void invalidMethodUsage();
105 void redirects();
106 void nonUtf8();
107 void nonUtf8_data();
108
109 void sendFileRequest();
110
111#if QT_CONFIG(process)
112 void sendFileRequestNotSet();
113 void sendFileRequestNoWrite();
114 void sendFileRequestNoRead();
115#endif
116
117 // WebDAV
118 void sendPropfind();
119 void sendPropfind_data();
120
121 // Attributes
122 void document();
123 void element();
124 void attr();
125 void text();
126 void cdata();
127
128 void noQmlContext();
129
130 // Crashes
131 // void outstanding_request_at_shutdown();
132
133 // void network_errors()
134 // void readyState()
135
136 void stateChangeCallingContext();
137
138private:
139 void doFileRequest(std::function<void(QObject *component, QTemporaryFile &writeFile)> verifyFunction);
140
141 QScopedPointer<QQmlEngine> engine;
142};
143
144void tst_qqmlxmlhttprequest::initTestCase()
145{
146 QQmlDataTest::initTestCase();
147
148 if (!qEnvironmentVariableIsSet(varName: "TEST_CUSTOM_PERMISSIONS")) {
149 qputenv(varName: "QML_XHR_ALLOW_FILE_READ", value: "1");
150 qputenv(varName: "QML_XHR_ALLOW_FILE_WRITE", value: "1");
151 }
152
153 engine.reset(other: new QQmlEngine);
154}
155
156// Test that the dom exception codes are correct
157void tst_qqmlxmlhttprequest::domExceptionCodes()
158{
159 QQmlComponent component(engine.get(), testFileUrl(fileName: "domExceptionCodes.qml"));
160 QScopedPointer<QObject> object(component.create());
161 QVERIFY(!object.isNull());
162
163 QCOMPARE(object->property("index_size_err").toInt(), 1);
164 QCOMPARE(object->property("domstring_size_err").toInt(), 2);
165 QCOMPARE(object->property("hierarchy_request_err").toInt(), 3);
166 QCOMPARE(object->property("wrong_document_err").toInt(), 4);
167 QCOMPARE(object->property("invalid_character_err").toInt(), 5);
168 QCOMPARE(object->property("no_data_allowed_err").toInt(), 6);
169 QCOMPARE(object->property("no_modification_allowed_err").toInt(), 7);
170 QCOMPARE(object->property("not_found_err").toInt(), 8);
171 QCOMPARE(object->property("not_supported_err").toInt(), 9);
172 QCOMPARE(object->property("inuse_attribute_err").toInt(), 10);
173 QCOMPARE(object->property("invalid_state_err").toInt(), 11);
174 QCOMPARE(object->property("syntax_err").toInt(), 12);
175 QCOMPARE(object->property("invalid_modification_err").toInt(), 13);
176 QCOMPARE(object->property("namespace_err").toInt(), 14);
177 QCOMPARE(object->property("invalid_access_err").toInt(), 15);
178 QCOMPARE(object->property("validation_err").toInt(), 16);
179 QCOMPARE(object->property("type_mismatch_err").toInt(), 17);
180}
181
182void tst_qqmlxmlhttprequest::callbackException_data()
183{
184 QTest::addColumn<QString>(name: "which");
185 QTest::addColumn<int>(name: "line");
186
187 QTest::newRow(dataTag: "on-opened") << "1" << 15;
188 QTest::newRow(dataTag: "on-loading") << "3" << 15;
189 QTest::newRow(dataTag: "on-done") << "4" << 15;
190}
191
192void tst_qqmlxmlhttprequest::callbackException()
193{
194 // Test exception reporting for exceptions thrown at various points.
195
196 QFETCH(QString, which);
197 QFETCH(int, line);
198
199 QString expect = testFileUrl(fileName: "callbackException.qml").toString() + ":"+QString::number(line)+": Error: Exception from Callback";
200 QTest::ignoreMessage(type: QtWarningMsg, message: expect.toLatin1());
201
202 QQmlComponent component(engine.get(), testFileUrl(fileName: "callbackException.qml"));
203 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
204 QVERIFY(!object.isNull());
205 object->setProperty(name: "url", value: "testdocument.html");
206 object->setProperty(name: "which", value: which);
207 component.completeCreate();
208
209 QTRY_VERIFY(object->property("threw").toBool());
210}
211
212// Test that the state value properties on the XMLHttpRequest constructor have the correct values.
213// ### WebKit does not do this, but it seems to fit the standard and QML better
214void tst_qqmlxmlhttprequest::staticStateValues()
215{
216 QQmlComponent component(engine.get(), testFileUrl(fileName: "staticStateValues.qml"));
217 QScopedPointer<QObject> object(component.create());
218 QVERIFY(!object.isNull());
219
220 QCOMPARE(object->property("unsent").toInt(), 0);
221 QCOMPARE(object->property("opened").toInt(), 1);
222 QCOMPARE(object->property("headers_received").toInt(), 2);
223 QCOMPARE(object->property("loading").toInt(), 3);
224 QCOMPARE(object->property("done").toInt(), 4);
225}
226
227// Test that the state value properties on instances have the correct values.
228void tst_qqmlxmlhttprequest::instanceStateValues()
229{
230 QQmlComponent component(engine.get(), testFileUrl(fileName: "instanceStateValues.qml"));
231 QScopedPointer<QObject> object(component.create());
232 QVERIFY(!object.isNull());
233
234 QCOMPARE(object->property("unsent").toInt(), 0);
235 QCOMPARE(object->property("opened").toInt(), 1);
236 QCOMPARE(object->property("headers_received").toInt(), 2);
237 QCOMPARE(object->property("loading").toInt(), 3);
238 QCOMPARE(object->property("done").toInt(), 4);
239}
240
241// Test calling constructor
242void tst_qqmlxmlhttprequest::constructor()
243{
244 QQmlComponent component(engine.get(), testFileUrl(fileName: "constructor.qml"));
245 QScopedPointer<QObject> object(component.create());
246 QVERIFY(!object.isNull());
247
248 QCOMPARE(object->property("calledAsConstructor").toBool(), true);
249 QCOMPARE(object->property("calledAsFunction").toBool(), true);
250}
251
252// Test that all the properties are set correctly before any request is sent
253void tst_qqmlxmlhttprequest::defaultState()
254{
255 QQmlComponent component(engine.get(), testFileUrl(fileName: "defaultState.qml"));
256 QScopedPointer<QObject> object(component.create());
257 QVERIFY(!object.isNull());
258
259 QCOMPARE(object->property("readState").toInt(), 0);
260 QCOMPARE(object->property("statusIsException").toBool(), true);
261 QCOMPARE(object->property("statusTextIsException").toBool(), true);
262 QCOMPARE(object->property("responseText").toString(), QString());
263 QCOMPARE(object->property("responseXMLIsNull").toBool(), true);
264}
265
266// Test valid XMLHttpRequest.open() calls
267void tst_qqmlxmlhttprequest::open()
268{
269 QFETCH(QUrl, qmlFile);
270 QFETCH(QString, url);
271 QFETCH(bool, remote);
272
273 TestHTTPServer server;
274 if (remote) {
275 QVERIFY2(server.listen(), qPrintable(server.errorString()));
276 QVERIFY(server.wait(testFileUrl("open_network.expect"),
277 testFileUrl("open_network.reply"),
278 testFileUrl("testdocument.html")));
279 url = server.urlString(documentPath: url);
280 }
281
282 QQmlComponent component(engine.get(), qmlFile);
283 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
284 QVERIFY(!object.isNull());
285 object->setProperty(name: "url", value: url);
286 component.completeCreate();
287
288 QCOMPARE(object->property("readyState").toBool(), true);
289 QCOMPARE(object->property("openedState").toBool(), true);
290 QCOMPARE(object->property("status").toBool(), true);
291 QCOMPARE(object->property("statusText").toBool(), true);
292 QCOMPARE(object->property("responseText").toBool(), true);
293 QCOMPARE(object->property("responseXML").toBool(), true);
294
295 QTRY_VERIFY(object->property("dataOK").toBool());
296}
297
298void tst_qqmlxmlhttprequest::open_data()
299{
300 QTest::addColumn<QUrl>(name: "qmlFile");
301 QTest::addColumn<QString>(name: "url");
302 QTest::addColumn<bool>(name: "remote");
303
304 QTest::newRow(dataTag: "Relative url)") << testFileUrl(fileName: "open.qml") << "testdocument.html" << false;
305 QTest::newRow(dataTag: "Absolute url)") << testFileUrl(fileName: "open.qml") << testFileUrl(fileName: "testdocument.html").toString() << false;
306 QTest::newRow(dataTag: "Absolute network url)") << testFileUrl(fileName: "open.qml") << "/testdocument.html" << true;
307
308 // ### Check that the username/password were sent to the server
309 QTest::newRow(dataTag: "User/pass") << testFileUrl(fileName: "open_user.qml") << "/testdocument.html" << true;
310}
311
312// Test that calling XMLHttpRequest.open() with an invalid method raises an exception
313void tst_qqmlxmlhttprequest::open_invalid_method()
314{
315 QQmlComponent component(engine.get(), testFileUrl(fileName: "open_invalid_method.qml"));
316 QScopedPointer<QObject> object(component.create());
317 QVERIFY(!object.isNull());
318
319 QCOMPARE(object->property("exceptionThrown").toBool(), true);
320}
321
322class TestThreadedHTTPServer : public QObject
323{
324 Q_OBJECT
325public:
326 TestThreadedHTTPServer(const QUrl &expectUrl, const QUrl &replyUrl, const QUrl &bodyUrl)
327 : m_server(nullptr) {
328 QMutexLocker locker(&m_lock);
329 moveToThread(thread: &m_thread);
330 m_thread.start();
331 QMetaObject::invokeMethod(obj: this, member: "start", type: Qt::QueuedConnection, Q_ARG(QUrl, expectUrl), Q_ARG(QUrl, replyUrl), Q_ARG(QUrl, bodyUrl));
332 m_startupCondition.wait(lockedMutex: &m_lock);
333 }
334 ~TestThreadedHTTPServer() {
335 m_server->deleteLater();
336 m_thread.exit();
337 m_thread.wait();
338 }
339
340 QUrl serverBaseUrl;
341
342private slots:
343 void start(const QUrl &expectUrl, const QUrl &replyUrl, const QUrl &bodyUrl) {
344 QMutexLocker locker(&m_lock);
345 m_server = new TestHTTPServer;
346 QVERIFY2(m_server->listen(), qPrintable(m_server->errorString()));
347 serverBaseUrl = m_server->baseUrl();
348 QVERIFY(m_server->wait(expectUrl, replyUrl, bodyUrl));
349 m_startupCondition.wakeAll();
350 }
351
352private:
353 TestHTTPServer *m_server;
354 QThread m_thread;
355 QMutex m_lock;
356 QWaitCondition m_startupCondition;
357};
358
359// Test that calling XMLHttpRequest.open() with sync
360void tst_qqmlxmlhttprequest::open_sync()
361{
362 TestThreadedHTTPServer server(testFileUrl(fileName: "open_network.expect"), testFileUrl(fileName: "open_network.reply"), testFileUrl(fileName: "testdocument.html"));
363
364 QQmlComponent component(engine.get(), testFileUrl(fileName: "open_sync.qml"));
365 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
366 QVERIFY(!object.isNull());
367 object->setProperty(name: "url", value: server.serverBaseUrl.resolved(QStringLiteral("/testdocument.html")).toString());
368 component.completeCreate();
369
370 QCOMPARE(object->property("responseText").toString(), QStringLiteral("QML Rocks!\n"));
371}
372
373// Calling with incorrect arg count raises an exception
374void tst_qqmlxmlhttprequest::open_arg_count()
375{
376 {
377 QQmlComponent component(engine.get(), testFileUrl(fileName: "open_arg_count.1.qml"));
378 QScopedPointer<QObject> object(component.create());
379 QVERIFY(!object.isNull());
380
381 QCOMPARE(object->property("exceptionThrown").toBool(), true);
382 }
383
384 {
385 QQmlComponent component(engine.get(), testFileUrl(fileName: "open_arg_count.2.qml"));
386 QScopedPointer<QObject> object(component.create());
387 QVERIFY(!object.isNull());
388
389 QCOMPARE(object->property("exceptionThrown").toBool(), true);
390 }
391}
392
393// Test valid setRequestHeader() calls
394void tst_qqmlxmlhttprequest::setRequestHeader()
395{
396 TestHTTPServer server;
397 QVERIFY2(server.listen(), qPrintable(server.errorString()));
398 QVERIFY(server.wait(testFileUrl("setRequestHeader.expect"),
399 testFileUrl("setRequestHeader.reply"),
400 testFileUrl("testdocument.html")));
401
402 QQmlComponent component(engine.get(), testFileUrl(fileName: "setRequestHeader.qml"));
403 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
404 QVERIFY(!object.isNull());
405 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
406 component.completeCreate();
407
408 QTRY_VERIFY(object->property("dataOK").toBool());
409}
410
411// Test valid setRequestHeader() calls with different header cases
412void tst_qqmlxmlhttprequest::setRequestHeader_caseInsensitive()
413{
414 TestHTTPServer server;
415 QVERIFY2(server.listen(), qPrintable(server.errorString()));
416 QVERIFY(server.wait(testFileUrl("setRequestHeader.expect"),
417 testFileUrl("setRequestHeader.reply"),
418 testFileUrl("testdocument.html")));
419
420 QQmlComponent component(engine.get(), testFileUrl(fileName: "setRequestHeader_caseInsensitive.qml"));
421 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
422 QVERIFY(!object.isNull());
423 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
424 component.completeCreate();
425
426 QTRY_VERIFY(object->property("dataOK").toBool());
427}
428// Test setting headers before open() throws exception
429void tst_qqmlxmlhttprequest::setRequestHeader_unsent()
430{
431 QQmlComponent component(engine.get(), testFileUrl(fileName: "setRequestHeader_unsent.qml"));
432 QScopedPointer<QObject> object(component.create());
433 QVERIFY(!object.isNull());
434
435 QCOMPARE(object->property("test").toBool(), true);
436}
437
438void tst_qqmlxmlhttprequest::setRequestHeader_illegalName_data()
439{
440 QTest::addColumn<QString>(name: "name");
441
442 QTest::newRow(dataTag: "Accept-Charset") << "AccePT-CHArset";
443 QTest::newRow(dataTag: "Accept-Encoding") << "AccEpt-EnCOding";
444 QTest::newRow(dataTag: "Connection") << "ConnECtion";
445 QTest::newRow(dataTag: "Content-Length") << "ContEnt-LenGth";
446 QTest::newRow(dataTag: "Cookie") << "CookIe";
447 QTest::newRow(dataTag: "Cookie2") << "CoOkie2";
448 QTest::newRow(dataTag: "Content-Transfer-Encoding") << "ConteNT-tRANSFER-eNCOding";
449 QTest::newRow(dataTag: "Date") << "DaTE";
450 QTest::newRow(dataTag: "Expect") << "ExPect";
451 QTest::newRow(dataTag: "Host") << "HoST";
452 QTest::newRow(dataTag: "Keep-Alive") << "KEEP-aLive";
453 QTest::newRow(dataTag: "Referer") << "ReferEr";
454 QTest::newRow(dataTag: "TE") << "Te";
455 QTest::newRow(dataTag: "Trailer") << "TraILEr";
456 QTest::newRow(dataTag: "Transfer-Encoding") << "tRANsfer-Encoding";
457 QTest::newRow(dataTag: "Upgrade") << "UpgrADe";
458 QTest::newRow(dataTag: "User-Agent") << "uSEr-Agent";
459 QTest::newRow(dataTag: "Via") << "vIa";
460 QTest::newRow(dataTag: "Proxy-") << "ProXy-";
461 QTest::newRow(dataTag: "Sec-") << "SeC-";
462 QTest::newRow(dataTag: "Proxy-*") << "Proxy-BLAH";
463 QTest::newRow(dataTag: "Sec-*") << "Sec-F";
464}
465
466// Tests that using illegal header names has no effect
467void tst_qqmlxmlhttprequest::setRequestHeader_illegalName()
468{
469 QFETCH(QString, name);
470
471 TestHTTPServer server;
472 QVERIFY2(server.listen(), qPrintable(server.errorString()));
473 QVERIFY(server.wait(testFileUrl("open_network.expect"),
474 testFileUrl("open_network.reply"),
475 testFileUrl("testdocument.html")));
476
477 QQmlComponent component(engine.get(), testFileUrl(fileName: "setRequestHeader_illegalName.qml"));
478 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
479 QVERIFY(!object.isNull());
480 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
481 object->setProperty(name: "header", value: name);
482 component.completeCreate();
483
484 QCOMPARE(object->property("readyState").toBool(), true);
485 QCOMPARE(object->property("openedState").toBool(), true);
486 QCOMPARE(object->property("status").toBool(), true);
487 QCOMPARE(object->property("statusText").toBool(), true);
488 QCOMPARE(object->property("responseText").toBool(), true);
489 QCOMPARE(object->property("responseXML").toBool(), true);
490
491 QTRY_VERIFY(object->property("dataOK").toBool());
492}
493
494// Test that attempting to set a header after a request is sent throws an exception
495void tst_qqmlxmlhttprequest::setRequestHeader_sent()
496{
497 TestHTTPServer server;
498 QVERIFY2(server.listen(), qPrintable(server.errorString()));
499 QVERIFY(server.wait(testFileUrl("open_network.expect"),
500 testFileUrl("open_network.reply"),
501 testFileUrl("testdocument.html")));
502
503 QQmlComponent component(engine.get(), testFileUrl(fileName: "setRequestHeader_sent.qml"));
504 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
505 QVERIFY(!object.isNull());
506 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
507 component.completeCreate();
508
509 QCOMPARE(object->property("test").toBool(), true);
510
511 QTRY_VERIFY(object->property("dataOK").toBool());
512}
513
514// Invalid arg count throws exception
515void tst_qqmlxmlhttprequest::setRequestHeader_args()
516{
517 QQmlComponent component(engine.get(), testFileUrl(fileName: "setRequestHeader_args.qml"));
518 QScopedPointer<QObject> object(component.create());
519 QVERIFY(!object.isNull());
520
521 QCOMPARE(object->property("exceptionThrown").toBool(), true);
522}
523
524// Test that calling send() in UNSENT state throws an exception
525void tst_qqmlxmlhttprequest::send_unsent()
526{
527 QQmlComponent component(engine.get(), testFileUrl(fileName: "send_unsent.qml"));
528 QScopedPointer<QObject> object(component.create());
529 QVERIFY(!object.isNull());
530
531 QCOMPARE(object->property("test").toBool(), true);
532}
533
534// Test attempting to resend a sent request throws an exception
535void tst_qqmlxmlhttprequest::send_alreadySent()
536{
537 QQmlComponent component(engine.get(), testFileUrl(fileName: "send_alreadySent.qml"));
538 QScopedPointer<QObject> object(component.create());
539 QVERIFY(!object.isNull());
540
541 QCOMPARE(object->property("test").toBool(), true);
542 QTRY_VERIFY(object->property("dataOK").toBool());
543}
544
545// Test that sends for GET, HEAD and DELETE ignore data
546void tst_qqmlxmlhttprequest::send_ignoreData()
547{
548 {
549 TestHTTPServer server;
550 QVERIFY2(server.listen(), qPrintable(server.errorString()));
551 QVERIFY(server.wait(testFileUrl("send_ignoreData_GET.expect"),
552 testFileUrl("send_ignoreData.reply"),
553 testFileUrl("testdocument.html")));
554
555 QQmlComponent component(engine.get(), testFileUrl(fileName: "send_ignoreData.qml"));
556 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
557 QVERIFY(!object.isNull());
558 object->setProperty(name: "reqType", value: "GET");
559 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
560 component.completeCreate();
561
562 QTRY_VERIFY(object->property("dataOK").toBool());
563 }
564
565 {
566 TestHTTPServer server;
567 QVERIFY2(server.listen(), qPrintable(server.errorString()));
568 QVERIFY(server.wait(testFileUrl("send_ignoreData_HEAD.expect"),
569 testFileUrl("send_ignoreData.reply"),
570 QUrl()));
571
572 QQmlComponent component(engine.get(), testFileUrl(fileName: "send_ignoreData.qml"));
573 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
574 QVERIFY(!object.isNull());
575 object->setProperty(name: "reqType", value: "HEAD");
576 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
577 component.completeCreate();
578
579 QTRY_VERIFY(object->property("dataOK").toBool());
580 }
581
582 {
583 TestHTTPServer server;
584 QVERIFY2(server.listen(), qPrintable(server.errorString()));
585 QVERIFY(server.wait(testFileUrl("send_ignoreData_DELETE.expect"),
586 testFileUrl("send_ignoreData.reply"),
587 QUrl()));
588
589 QQmlComponent component(engine.get(), testFileUrl(fileName: "send_ignoreData.qml"));
590 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
591 QVERIFY(!object.isNull());
592 object->setProperty(name: "reqType", value: "DELETE");
593 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
594 component.completeCreate();
595
596 QTRY_VERIFY(object->property("dataOK").toBool());
597 }
598}
599
600// Test that send()'ing data works
601void tst_qqmlxmlhttprequest::send_withdata()
602{
603 QFETCH(QString, file_expected);
604 QFETCH(QString, file_qml);
605
606 TestHTTPServer server;
607 QVERIFY2(server.listen(), qPrintable(server.errorString()));
608 QVERIFY(server.wait(testFileUrl(file_expected),
609 testFileUrl("send_data.reply"),
610 testFileUrl("testdocument.html")));
611
612 QQmlComponent component(engine.get(), testFileUrl(fileName: file_qml));
613 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
614 QVERIFY(!object.isNull());
615 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
616 component.completeCreate();
617
618 QTRY_VERIFY(object->property("dataOK").toBool());
619}
620
621void tst_qqmlxmlhttprequest::send_withdata_data()
622{
623 QTest::addColumn<QString>(name: "file_expected");
624 QTest::addColumn<QString>(name: "file_qml");
625
626 QTest::newRow(dataTag: "No content-type") << "send_data.1.expect" << "send_data.1.qml";
627 QTest::newRow(dataTag: "Correct content-type") << "send_data.1.expect" << "send_data.2.qml";
628 QTest::newRow(dataTag: "Incorrect content-type") << "send_data.1.expect" << "send_data.3.qml";
629 QTest::newRow(dataTag: "Correct content-type - out of order") << "send_data.4.expect" << "send_data.4.qml";
630 QTest::newRow(dataTag: "Incorrect content-type - out of order") << "send_data.4.expect" << "send_data.5.qml";
631 QTest::newRow(dataTag: "PUT") << "send_data.6.expect" << "send_data.6.qml";
632 QTest::newRow(dataTag: "Correct content-type - no charset") << "send_data.1.expect" << "send_data.7.qml";
633 QTest::newRow(dataTag: "ArrayBuffer") << "send_data.11.expect" << "send_data.11.qml";
634}
635
636void tst_qqmlxmlhttprequest::send_options()
637{
638 QFETCH(QString, url_suffix);
639 QFETCH(QString, file_expected);
640 QFETCH(QString, file_qml);
641 QFETCH(QString, file_reply);
642
643 TestHTTPServer server;
644 QVERIFY2(server.listen(), qPrintable(server.errorString()));
645 QVERIFY(server.wait(testFileUrl(file_expected),
646 testFileUrl(file_reply),
647 testFileUrl("testdocument.html")));
648
649 QQmlComponent component(engine.get(), testFileUrl(fileName: file_qml));
650 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
651 QVERIFY(!object.isNull());
652 QString url = server.baseUrl().toString();
653 if (url_suffix != "/")
654 url.append(c: QLatin1Char('/'));
655 if (!url_suffix.isEmpty())
656 url.append(s: url_suffix);
657 object->setProperty(name: "url", value: url);
658 component.completeCreate();
659
660 QTRY_VERIFY(object->property("dataOK").toBool());
661 QTRY_VERIFY(object->property("headerOK").toBool());
662}
663
664void tst_qqmlxmlhttprequest::send_options_data()
665{
666 QTest::addColumn<QString>(name: "url_suffix");
667 QTest::addColumn<QString>(name: "file_expected");
668 QTest::addColumn<QString>(name: "file_qml");
669 QTest::addColumn<QString>(name: "file_reply");
670
671 QTest::newRow(dataTag: "OPTIONS (no data, no resource, no path)") << "" << "send_data.8.expect" << "send_data.8.qml" << "send_data.2.reply";
672 QTest::newRow(dataTag: "OPTIONS (no data, no resource, path- \"/\")") << "/" << "send_data.8.expect" << "send_data.8.qml" << "send_data.2.reply";
673 QTest::newRow(dataTag: "OPTIONS (no data, with resource)") << "testdocument.html" << "send_data.9.expect" << "send_data.8.qml" << "send_data.2.reply";
674 QTest::newRow(dataTag: "OPTIONS (with data)") << "testdocument.html" << "send_data.10.expect" << "send_data.9.qml" << "send_data.2.reply";
675}
676
677void tst_qqmlxmlhttprequest::send_patch()
678{
679 TestHTTPServer server;
680 QVERIFY2(server.listen(), qPrintable(server.errorString()));
681 QVERIFY(server.wait(testFileUrl("send_patch.expect"),
682 testFileUrl("send_patch.reply"),
683 // the content of response file will be ignored due to 204 status code
684 testFileUrl("testdocument.html")));
685
686 QQmlComponent component(engine.get(), testFileUrl(fileName: "send_patch.qml"));
687 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
688 QVERIFY(!object.isNull());
689 object->setProperty(name: "url", value: server.urlString(documentPath: "/qqmlxmlhttprequest.cpp"));
690 component.completeCreate();
691
692 QTRY_VERIFY(object->property("dataOK").toBool());
693 QTRY_VERIFY(object->property("headerOK").toBool());
694}
695
696
697// Test abort() has no effect in unsent state
698void tst_qqmlxmlhttprequest::abort_unsent()
699{
700 QQmlComponent component(engine.get(), testFileUrl(fileName: "abort_unsent.qml"));
701 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
702 QVERIFY(!object.isNull());
703 object->setProperty(name: "url", value: "testdocument.html");
704 component.completeCreate();
705
706 QCOMPARE(object->property("readyState").toBool(), true);
707 QCOMPARE(object->property("openedState").toBool(), true);
708 QCOMPARE(object->property("status").toBool(), true);
709 QCOMPARE(object->property("statusText").toBool(), true);
710 QCOMPARE(object->property("responseText").toBool(), true);
711 QCOMPARE(object->property("responseXML").toBool(), true);
712
713 QTRY_VERIFY(object->property("dataOK").toBool());
714}
715
716// Test abort() cancels an open (but unsent) request
717void tst_qqmlxmlhttprequest::abort_opened()
718{
719 QQmlComponent component(engine.get(), testFileUrl(fileName: "abort_opened.qml"));
720 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
721 QVERIFY(!object.isNull());
722 object->setProperty(name: "url", value: "testdocument.html");
723 component.completeCreate();
724
725 QCOMPARE(object->property("readyState").toBool(), true);
726 QCOMPARE(object->property("openedState").toBool(), true);
727 QCOMPARE(object->property("status").toBool(), true);
728 QCOMPARE(object->property("statusText").toBool(), true);
729 QCOMPARE(object->property("responseText").toBool(), true);
730 QCOMPARE(object->property("responseXML").toBool(), true);
731
732 QTRY_VERIFY(object->property("dataOK").toBool());
733}
734
735// Test abort() aborts in progress send
736void tst_qqmlxmlhttprequest::abort()
737{
738 TestHTTPServer server;
739 QVERIFY2(server.listen(), qPrintable(server.errorString()));
740 QVERIFY(server.wait(testFileUrl("abort.expect"),
741 testFileUrl("abort.reply"),
742 testFileUrl("testdocument.html")));
743
744 QQmlComponent component(engine.get(), testFileUrl(fileName: "abort.qml"));
745 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
746 QVERIFY(!object.isNull());
747 const QUrl url = server.url(documentPath: "/testdocument.html");
748 QUrl dummyUrl = url;
749 dummyUrl.setPort(dummyUrl.port() - 1);
750 object->setProperty(name: "urlDummy", value: dummyUrl.toString());
751 object->setProperty(name: "url", value: url.toString());
752 component.completeCreate();
753
754 QCOMPARE(object->property("seenDone").toBool(), true);
755 QCOMPARE(object->property("didNotSeeUnsent").toBool(), true);
756 QCOMPARE(object->property("endStateUnsent").toBool(), true);
757
758 QTRY_VERIFY(object->property("dataOK").toBool());
759}
760
761void tst_qqmlxmlhttprequest::getResponseHeader()
762{
763 QQmlEngine engine; // Avoid cookie contamination
764
765 TestHTTPServer server;
766 QVERIFY2(server.listen(), qPrintable(server.errorString()));
767 QVERIFY(server.wait(testFileUrl("getResponseHeader.expect"),
768 testFileUrl("getResponseHeader.reply"),
769 testFileUrl("testdocument.html")));
770
771
772 QQmlComponent component(&engine, testFileUrl(fileName: "getResponseHeader.qml"));
773 QScopedPointer<QObject> object(component.beginCreate(engine.rootContext()));
774 QVERIFY(!object.isNull());
775 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
776 component.completeCreate();
777
778 QCOMPARE(object->property("unsentException").toBool(), true);
779 QCOMPARE(object->property("openedException").toBool(), true);
780 QCOMPARE(object->property("readyState").toBool(), true);
781 QCOMPARE(object->property("openedState").toBool(), true);
782
783 QTRY_VERIFY(object->property("dataOK").toBool());
784
785 QCOMPARE(object->property("headersReceivedState").toBool(), true);
786 QCOMPARE(object->property("headersReceivedNullHeader").toBool(), true);
787 QCOMPARE(object->property("headersReceivedValidHeader").toBool(), true);
788 QCOMPARE(object->property("headersReceivedMultiValidHeader").toBool(), true);
789 QCOMPARE(object->property("headersReceivedCookieHeader").toBool(), true);
790
791 QCOMPARE(object->property("doneState").toBool(), true);
792 QCOMPARE(object->property("doneNullHeader").toBool(), true);
793 QCOMPARE(object->property("doneValidHeader").toBool(), true);
794 QCOMPARE(object->property("doneMultiValidHeader").toBool(), true);
795 QCOMPARE(object->property("doneCookieHeader").toBool(), true);
796}
797
798// Test getResponseHeader throws an exception in an invalid state
799void tst_qqmlxmlhttprequest::getResponseHeader_unsent()
800{
801 QQmlComponent component(engine.get(), testFileUrl(fileName: "getResponseHeader_unsent.qml"));
802 QScopedPointer<QObject> object(component.create());
803 QVERIFY(!object.isNull());
804
805 QCOMPARE(object->property("test").toBool(), true);
806}
807
808// Test getResponseHeader throws an exception in an invalid state
809void tst_qqmlxmlhttprequest::getResponseHeader_sent()
810{
811 QQmlComponent component(engine.get(), testFileUrl(fileName: "getResponseHeader_sent.qml"));
812 QScopedPointer<QObject> object(component.create());
813 QVERIFY(!object.isNull());
814
815 QCOMPARE(object->property("test").toBool(), true);
816}
817
818// Invalid arg count throws exception
819void tst_qqmlxmlhttprequest::getResponseHeader_args()
820{
821 QQmlComponent component(engine.get(), testFileUrl(fileName: "getResponseHeader_args.qml"));
822 QScopedPointer<QObject> object(component.create());
823 QVERIFY(!object.isNull());
824
825 QTRY_VERIFY(object->property("exceptionThrown").toBool());
826}
827
828void tst_qqmlxmlhttprequest::getAllResponseHeaders()
829{
830 QQmlEngine engine; // Avoid cookie contamination
831
832 TestHTTPServer server;
833 QVERIFY2(server.listen(), qPrintable(server.errorString()));
834 QVERIFY(server.wait(testFileUrl("getResponseHeader.expect"),
835 testFileUrl("getResponseHeader.reply"),
836 testFileUrl("testdocument.html")));
837
838 QQmlComponent component(&engine, testFileUrl(fileName: "getAllResponseHeaders.qml"));
839 QScopedPointer<QObject> object(component.beginCreate(engine.rootContext()));
840 QVERIFY(!object.isNull());
841 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
842 component.completeCreate();
843
844 QCOMPARE(object->property("unsentException").toBool(), true);
845 QCOMPARE(object->property("openedException").toBool(), true);
846 QCOMPARE(object->property("readyState").toBool(), true);
847 QCOMPARE(object->property("openedState").toBool(), true);
848
849 QTRY_VERIFY(object->property("dataOK").toBool());
850
851 QCOMPARE(object->property("headersReceivedState").toBool(), true);
852 QCOMPARE(object->property("headersReceivedHeader").toBool(), true);
853
854 QCOMPARE(object->property("doneState").toBool(), true);
855 QCOMPARE(object->property("doneHeader").toBool(), true);
856}
857
858// Test getAllResponseHeaders throws an exception in an invalid state
859void tst_qqmlxmlhttprequest::getAllResponseHeaders_unsent()
860{
861 QQmlComponent component(engine.get(), testFileUrl(fileName: "getAllResponseHeaders_unsent.qml"));
862 QScopedPointer<QObject> object(component.create());
863 QVERIFY(!object.isNull());
864
865 QCOMPARE(object->property("test").toBool(), true);
866}
867
868// Test getAllResponseHeaders throws an exception in an invalid state
869void tst_qqmlxmlhttprequest::getAllResponseHeaders_sent()
870{
871 QQmlComponent component(engine.get(), testFileUrl(fileName: "getAllResponseHeaders_sent.qml"));
872 QScopedPointer<QObject> object(component.create());
873 QVERIFY(!object.isNull());
874
875 QCOMPARE(object->property("test").toBool(), true);
876}
877
878// Invalid arg count throws exception
879void tst_qqmlxmlhttprequest::getAllResponseHeaders_args()
880{
881 QQmlComponent component(engine.get(), testFileUrl(fileName: "getAllResponseHeaders_args.qml"));
882 QScopedPointer<QObject> object(component.create());
883 QVERIFY(!object.isNull());
884
885 QTRY_VERIFY(object->property("exceptionThrown").toBool());
886}
887
888void tst_qqmlxmlhttprequest::getBinaryData()
889{
890 TestHTTPServer server;
891 QVERIFY2(server.listen(), qPrintable(server.errorString()));
892 QVERIFY(server.wait(testFileUrl("receive_binary_data.expect"),
893 testFileUrl("receive_binary_data.reply"),
894 testFileUrl("qml_logo.png")));
895
896 QQmlComponent component(engine.get(), testFileUrl(fileName: "receiveBinaryData.qml"));
897 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
898 QVERIFY(!object.isNull());
899 object->setProperty(name: "url", value: server.urlString(documentPath: "/gml_logo.png"));
900 component.completeCreate();
901
902 QFileInfo fileInfo("data/qml_logo.png");
903 QTRY_COMPARE(object->property("readSize").toInt(), fileInfo.size());
904 QCOMPARE(object->property("status").toInt(), 200);
905}
906
907void tst_qqmlxmlhttprequest::getJsonData()
908{
909 TestHTTPServer server;
910 QVERIFY2(server.listen(), qPrintable(server.errorString()));
911 QVERIFY(server.wait(testFileUrl("receive_json_data.expect"),
912 testFileUrl("receive_binary_data.reply"),
913 testFileUrl("json.data")));
914
915 QQmlComponent component(engine.get(), testFileUrl(fileName: "receiveJsonData.qml"));
916 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
917 QVERIFY(!object.isNull());
918 object->setProperty(name: "url", value: server.urlString(documentPath: "/json.data"));
919 component.completeCreate();
920
921 QTRY_VERIFY(object->property("result").toBool());
922}
923
924void tst_qqmlxmlhttprequest::status()
925{
926 QFETCH(QUrl, replyUrl);
927 QFETCH(int, status);
928
929 TestHTTPServer server;
930 QVERIFY2(server.listen(), qPrintable(server.errorString()));
931 QVERIFY(server.wait(testFileUrl("status.expect"),
932 replyUrl,
933 testFileUrl("testdocument.html")));
934
935 QQmlComponent component(engine.get(), testFileUrl(fileName: "status.qml"));
936 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
937 QVERIFY(!object.isNull());
938 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
939 object->setProperty(name: "expectedStatus", value: status);
940 component.completeCreate();
941
942 QTRY_VERIFY(object->property("dataOK").toBool());
943
944 QCOMPARE(object->property("unsentException").toBool(), true);
945 QCOMPARE(object->property("openedException").toBool(), true);
946 QCOMPARE(object->property("sentException").toBool(), true);
947 QCOMPARE(object->property("headersReceived").toBool(), true);
948 QCOMPARE(object->property("loading").toBool(), true);
949 QCOMPARE(object->property("done").toBool(), true);
950 QCOMPARE(object->property("resetException").toBool(), true);
951 QCOMPARE(object->property("onloadCalled").toBool(), true);
952 QCOMPARE(object->property("onloadendCalled").toBool(), true);
953 QCOMPARE(object->property("onerrorCalled").toBool(), false);
954}
955
956void tst_qqmlxmlhttprequest::status_data()
957{
958 QTest::addColumn<QUrl>(name: "replyUrl");
959 QTest::addColumn<int>(name: "status");
960
961 QTest::newRow(dataTag: "OK") << testFileUrl(fileName: "status.200.reply") << 200;
962 QTest::newRow(dataTag: "Not Found") << testFileUrl(fileName: "status.404.reply") << 404;
963 QTest::newRow(dataTag: "Bad Request") << testFileUrl(fileName: "status.400.reply") << 400;
964}
965
966void tst_qqmlxmlhttprequest::statusText()
967{
968 QFETCH(QUrl, replyUrl);
969 QFETCH(QString, statusText);
970
971 TestHTTPServer server;
972 QVERIFY2(server.listen(), qPrintable(server.errorString()));
973 QVERIFY(server.wait(testFileUrl("status.expect"),
974 replyUrl,
975 testFileUrl("testdocument.html")));
976
977 QQmlComponent component(engine.get(), testFileUrl(fileName: "statusText.qml"));
978 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
979 QVERIFY(!object.isNull());
980 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
981 object->setProperty(name: "expectedStatus", value: statusText);
982 component.completeCreate();
983
984 QTRY_VERIFY(object->property("dataOK").toBool());
985
986 QCOMPARE(object->property("unsentException").toBool(), true);
987 QCOMPARE(object->property("openedException").toBool(), true);
988 QCOMPARE(object->property("sentException").toBool(), true);
989 QCOMPARE(object->property("headersReceived").toBool(), true);
990 QCOMPARE(object->property("loading").toBool(), true);
991 QCOMPARE(object->property("done").toBool(), true);
992 QCOMPARE(object->property("resetException").toBool(), true);
993}
994
995void tst_qqmlxmlhttprequest::statusText_data()
996{
997 QTest::addColumn<QUrl>(name: "replyUrl");
998 QTest::addColumn<QString>(name: "statusText");
999
1000 QTest::newRow(dataTag: "OK") << testFileUrl(fileName: "status.200.reply") << "OK";
1001 QTest::newRow(dataTag: "Not Found") << testFileUrl(fileName: "status.404.reply") << "Document not found";
1002 QTest::newRow(dataTag: "Bad Request") << testFileUrl(fileName: "status.400.reply") << "Bad request";
1003}
1004
1005void tst_qqmlxmlhttprequest::responseText()
1006{
1007 QFETCH(QUrl, replyUrl);
1008 QFETCH(QUrl, bodyUrl);
1009 QFETCH(QString, responseText);
1010
1011 TestHTTPServer server;
1012 QVERIFY2(server.listen(), qPrintable(server.errorString()));
1013 QVERIFY(server.wait(testFileUrl("status.expect"),
1014 replyUrl,
1015 bodyUrl));
1016
1017 QQmlComponent component(engine.get(), testFileUrl(fileName: "responseText.qml"));
1018 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
1019 QVERIFY(!object.isNull());
1020 object->setProperty(name: "url", value: server.urlString(documentPath: "/testdocument.html"));
1021 object->setProperty(name: "expectedText", value: responseText);
1022 component.completeCreate();
1023
1024 QTRY_VERIFY(object->property("dataOK").toBool());
1025
1026 QCOMPARE(object->property("unsent").toBool(), true);
1027 QCOMPARE(object->property("opened").toBool(), true);
1028 QCOMPARE(object->property("sent").toBool(), true);
1029 QCOMPARE(object->property("headersReceived").toBool(), true);
1030 QCOMPARE(object->property("loading").toBool(), true);
1031 QCOMPARE(object->property("done").toBool(), true);
1032 QCOMPARE(object->property("reset").toBool(), true);
1033}
1034
1035void tst_qqmlxmlhttprequest::responseText_data()
1036{
1037 QTest::addColumn<QUrl>(name: "replyUrl");
1038 QTest::addColumn<QUrl>(name: "bodyUrl");
1039 QTest::addColumn<QString>(name: "responseText");
1040
1041 QTest::newRow(dataTag: "OK") << testFileUrl(fileName: "status.200.reply") << testFileUrl(fileName: "testdocument.html") << "QML Rocks!\n";
1042 QTest::newRow(dataTag: "empty body") << testFileUrl(fileName: "status.200.reply") << QUrl() << "";
1043 QTest::newRow(dataTag: "Not Found") << testFileUrl(fileName: "status.404.reply") << testFileUrl(fileName: "testdocument.html") << "QML Rocks!\n";
1044 QTest::newRow(dataTag: "Bad Request") << testFileUrl(fileName: "status.400.reply") << testFileUrl(fileName: "testdocument.html") << "QML Rocks!\n";
1045 QTest::newRow(dataTag: "Internal server error") << testFileUrl(fileName: "status.500.reply") << testFileUrl(fileName: "testdocument.html") << "QML Rocks!\n";
1046}
1047
1048void tst_qqmlxmlhttprequest::nonUtf8()
1049{
1050 QFETCH(QString, fileName);
1051 QFETCH(QString, responseText);
1052 QFETCH(QString, xmlRootNodeValue);
1053
1054 QQmlComponent component(engine.get(), testFileUrl(fileName: "utf16.qml"));
1055 QScopedPointer<QObject> object(component.create());
1056 QVERIFY(!object.isNull());
1057
1058 object->setProperty(name: "fileName", value: fileName);
1059 QMetaObject::invokeMethod(obj: object.data(), member: "startRequest");
1060
1061 QTRY_VERIFY(object->property("dataOK").toBool());
1062
1063 QCOMPARE(object->property("responseText").toString(), responseText);
1064
1065 if (!xmlRootNodeValue.isEmpty()) {
1066 QString rootNodeValue = object->property(name: "responseXmlRootNodeValue").toString();
1067 QCOMPARE(rootNodeValue, xmlRootNodeValue);
1068 }
1069}
1070
1071void tst_qqmlxmlhttprequest::nonUtf8_data()
1072{
1073 QTest::addColumn<QString>(name: "fileName");
1074 QTest::addColumn<QString>(name: "responseText");
1075 QTest::addColumn<QString>(name: "xmlRootNodeValue");
1076
1077 QString uc;
1078 uc.resize(size: 3);
1079 uc[0] = QChar(0x10e3);
1080 uc[1] = QChar(' ');
1081 uc[2] = QChar(0x03a3);
1082
1083 QTest::newRow(dataTag: "responseText") << "utf16.html" << uc + '\n' << "";
1084 QTest::newRow(dataTag: "responseXML") << "utf16.xml" << "<?xml version=\"1.0\" encoding=\"UTF-16\" standalone='yes'?>\n<root>\n" + uc + "\n</root>\n" << QString('\n' + uc + '\n');
1085}
1086
1087static const QString testString = QStringLiteral("Test-String");
1088
1089void tst_qqmlxmlhttprequest::doFileRequest(std::function<void(QObject *component, QTemporaryFile &writeFile)> verifyFunction)
1090{
1091 // Create test files
1092 QTemporaryFile writeFile;
1093 QTemporaryFile readFile;
1094
1095 writeFile.open();
1096 writeFile.close();
1097
1098 QVERIFY(readFile.open());
1099 readFile.write(data: testString.toUtf8());
1100 readFile.close();
1101
1102 // Avoid cached environment variables
1103 QQmlEngine engine;
1104
1105 QQmlComponent component(&engine, testFileUrl(fileName: "file_request.qml"));
1106
1107 const QVariantMap properties = {
1108 {"writeURL", QUrl::fromLocalFile(localfile: writeFile.fileName()).toString()},
1109 {"readURL", QUrl::fromLocalFile(localfile: readFile.fileName()).toString()}
1110 };
1111
1112 QScopedPointer<QObject> object(component.createWithInitialProperties(initialProperties: properties, context: engine.rootContext()));
1113 QVERIFY(!object.isNull());
1114
1115 verifyFunction(object.get(), writeFile);
1116}
1117
1118// Test file:// requests
1119void tst_qqmlxmlhttprequest::sendFileRequest()
1120{
1121 // Test with both writing and reading allowed
1122 doFileRequest(verifyFunction: [](QObject* object, QTemporaryFile &writeFile) {
1123 QTRY_COMPARE(object->property("readResult").toString(), testString);
1124
1125 QTRY_VERIFY(object->property("writeDone").toBool());
1126
1127 QVERIFY(writeFile.open());
1128 QCOMPARE(QString::fromUtf8(writeFile.readAll()), testString);
1129 writeFile.close();
1130 });
1131}
1132
1133#if QT_CONFIG(process)
1134void tst_qqmlxmlhttprequest::sendFileRequestNotSet() {
1135 if (qEnvironmentVariableIsSet(varName: "TEST_CUSTOM_PERMISSIONS")) {
1136 // Test with no settings
1137 // Should just result in warnings in Qt 5
1138 doFileRequest(verifyFunction: [](QObject* object, QTemporaryFile &writeFile) {
1139 QTRY_COMPARE(object->property("readResult").toString(), testString);
1140
1141 QTRY_VERIFY(object->property("writeDone").toBool());
1142
1143 QVERIFY(writeFile.open());
1144 QCOMPARE(QString::fromUtf8(writeFile.readAll()), testString);
1145 writeFile.close();
1146 });
1147 return;
1148 }
1149
1150 QProcess child;
1151 child.setProgram(QCoreApplication::applicationFilePath());
1152 child.setArguments(QStringList(QLatin1String("sendFileRequestNotSet")));
1153 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
1154 env.insert(name: QLatin1String("TEST_CUSTOM_PERMISSIONS"), value: QLatin1String("1"));
1155 env.remove(name: "QML_XHR_ALLOW_FILE_WRITE");
1156 env.remove(name: "QML_XHR_ALLOW_FILE_READ");
1157 child.setProcessEnvironment(env);
1158 child.start();
1159 QVERIFY(child.waitForFinished());
1160
1161 // Check exit code
1162 QCOMPARE(child.exitCode(), 0);
1163
1164 // Check if all warnings were printed
1165 QString output = QString::fromUtf8(str: child.readAllStandardOutput());
1166
1167
1168 const QString readingWarning = QLatin1String(
1169 "XMLHttpRequest: Using GET on a local file is dangerous "
1170 "and will be disabled by default in a future Qt version."
1171 "Set QML_XHR_ALLOW_FILE_READ to 1 if you wish to continue using this feature.");
1172
1173 const QString writingWarning = QLatin1String(
1174 "XMLHttpRequest: Using PUT on a local file is dangerous "
1175 "and will be disabled by default in a future Qt version."
1176 "Set QML_XHR_ALLOW_FILE_WRITE to 1 if you wish to continue using this feature.");
1177
1178 QVERIFY(output.contains(readingWarning));
1179 QVERIFY(output.contains(writingWarning));
1180}
1181#endif
1182
1183#if QT_CONFIG(process)
1184void tst_qqmlxmlhttprequest::sendFileRequestNoWrite() {
1185 if (qEnvironmentVariableIsSet(varName: "TEST_CUSTOM_PERMISSIONS")) {
1186 // Test with no writing enabled
1187 doFileRequest(verifyFunction: [](QObject* object, QTemporaryFile &writeFile) {
1188 QTRY_COMPARE(object->property("readResult").toString(), testString);
1189
1190 // Check that the file stays empty
1191 QVERIFY(writeFile.open());
1192 QCOMPARE(QString::fromUtf8(writeFile.readAll()), "");
1193 writeFile.close();
1194 });
1195 return;
1196 }
1197
1198 QProcess child;
1199 child.setProgram(QCoreApplication::applicationFilePath());
1200 child.setArguments(QStringList(QLatin1String("sendFileRequestNoWrite")));
1201 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
1202 env.insert(name: QLatin1String("TEST_CUSTOM_PERMISSIONS"), value: QLatin1String("1"));
1203 env.insert(name: QLatin1String("QML_XHR_ALLOW_FILE_WRITE"), value: QLatin1String("0"));
1204 env.insert(name: QLatin1String("QML_XHR_ALLOW_FILE_READ"), value: QLatin1String("1"));
1205 child.setProcessEnvironment(env);
1206 child.start();
1207 QVERIFY(child.waitForFinished());
1208 QCOMPARE(child.exitCode(), 0);
1209}
1210#endif
1211
1212#if QT_CONFIG(process)
1213void tst_qqmlxmlhttprequest::sendFileRequestNoRead() {
1214 if (qEnvironmentVariableIsSet(varName: "TEST_CUSTOM_PERMISSIONS")) {
1215 // Test with no reading enabled
1216 doFileRequest(verifyFunction: [](QObject* object, QTemporaryFile &writeFile) {
1217 // Check that the write happens
1218 QTRY_VERIFY(object->property("writeDone").toBool());
1219
1220 QVERIFY(writeFile.open());
1221 QCOMPARE(QString::fromUtf8(writeFile.readAll()), testString);
1222 writeFile.close();
1223
1224 // Verify that the read has not yielded any value
1225 QVERIFY(object->property("readResult").isNull());
1226 });
1227 return;
1228 }
1229
1230 QProcess child;
1231 child.setProgram(QCoreApplication::applicationFilePath());
1232 child.setArguments(QStringList(QLatin1String("sendFileRequestNoRead")));
1233 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
1234 env.insert(name: QLatin1String("TEST_CUSTOM_PERMISSIONS"), value: QLatin1String("1"));
1235 env.insert(name: QLatin1String("QML_XHR_ALLOW_FILE_WRITE"), value: QLatin1String("1"));
1236 env.insert(name: QLatin1String("QML_XHR_ALLOW_FILE_READ"), value: QLatin1String("0"));
1237 child.setProcessEnvironment(env);
1238 child.start();
1239 QVERIFY(child.waitForFinished());
1240 QCOMPARE(child.exitCode(), 0);
1241}
1242#endif
1243
1244void tst_qqmlxmlhttprequest::sendPropfind()
1245{
1246 const QString prefix = "WebDAV//";
1247
1248 QFETCH(QString, qml);
1249 QFETCH(QString, resource);
1250 QFETCH(QString, expectedFile);
1251 QFETCH(QString, replyHeader);
1252 QFETCH(QString, replyBody);
1253
1254 TestHTTPServer server;
1255 QVERIFY2(server.listen(), qPrintable(server.errorString()));
1256
1257 QVERIFY(server.wait(testFileUrl(prefix + expectedFile),
1258 testFileUrl(prefix + replyHeader),
1259 testFileUrl(prefix + replyBody)));
1260
1261 QQmlComponent component(engine.get(), testFileUrl(fileName: prefix + qml));
1262 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
1263 QVERIFY(!object.isNull());
1264 object->setProperty(name: "url", value: server.urlString(documentPath: resource));
1265 component.completeCreate();
1266
1267 QTRY_VERIFY(object->property("xmlTest").toBool());
1268 QCOMPARE(object->property("typeTest").toBool(), true);
1269}
1270
1271void tst_qqmlxmlhttprequest::sendPropfind_data()
1272{
1273 QTest::addColumn<QString>(name: "qml");
1274 QTest::addColumn<QString>(name: "resource");
1275 QTest::addColumn<QString>(name: "expectedFile");
1276 QTest::addColumn<QString>(name: "replyHeader");
1277 QTest::addColumn<QString>(name: "replyBody");
1278
1279 QTest::newRow(dataTag: "Send PROPFIND for file (bigbox, author, DingALing, Random properties). Get response with responseXML.") << "sendPropfind.responseXML.qml" << "/file" << "propfind.file.expect" << "propfind.file.reply.header" << "propfind.file.reply.body";
1280 QTest::newRow(dataTag: "Send PROPFIND for file (bigbox, author, DingALing, Random properties). Get response with response.") << "sendPropfind.response.qml" << "/file" << "propfind.file.expect" << "propfind.file.reply.header" << "propfind.file.reply.body";
1281 QTest::newRow(dataTag: "Send PROPFIND \"allprop\" request for collection.") << "sendPropfind.collection.allprop.qml" << "/container/" << "propfind.collection.allprop.expect" << "propfind.file.reply.header" << "propfind.collection.allprop.reply.body";
1282}
1283
1284// Test that calling hte XMLHttpRequest methods on a non-XMLHttpRequest object
1285// throws an exception
1286void tst_qqmlxmlhttprequest::invalidMethodUsage()
1287{
1288 QQmlComponent component(engine.get(), testFileUrl(fileName: "invalidMethodUsage.qml"));
1289 QScopedPointer<QObject> object(component.create());
1290 QVERIFY(!object.isNull());
1291
1292 QCOMPARE(object->property("readyState").toBool(), true);
1293 QCOMPARE(object->property("status").toBool(), true);
1294 QCOMPARE(object->property("statusText").toBool(), true);
1295 QCOMPARE(object->property("responseText").toBool(), true);
1296 QCOMPARE(object->property("responseXML").toBool(), true);
1297
1298 QCOMPARE(object->property("open").toBool(), true);
1299 QCOMPARE(object->property("setRequestHeader").toBool(), true);
1300 QCOMPARE(object->property("send").toBool(), true);
1301 QCOMPARE(object->property("abort").toBool(), true);
1302 QCOMPARE(object->property("getResponseHeader").toBool(), true);
1303 QCOMPARE(object->property("getAllResponseHeaders").toBool(), true);
1304}
1305
1306// Test that XMLHttpRequest transparently redirects
1307void tst_qqmlxmlhttprequest::redirects()
1308{
1309 {
1310 TestHTTPServer server;
1311 QVERIFY2(server.listen(), qPrintable(server.errorString()));
1312 server.addRedirect(filename: "redirect.html", redirectName: server.urlString(documentPath: "/redirecttarget.html"));
1313 server.serveDirectory(dataDirectory());
1314
1315 QQmlComponent component(engine.get(), testFileUrl(fileName: "redirects.qml"));
1316 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
1317 QVERIFY(!object.isNull());
1318 object->setProperty(name: "url", value: server.urlString(documentPath: "/redirect.html"));
1319 object->setProperty(name: "expectedText", value: "");
1320 component.completeCreate();
1321
1322 QTRY_VERIFY(object->property("done").toBool());
1323 QCOMPARE(object->property("dataOK").toBool(), true);
1324 }
1325
1326 {
1327 TestHTTPServer server;
1328 QVERIFY2(server.listen(), qPrintable(server.errorString()));
1329 server.addRedirect(filename: "redirect.html", redirectName: server.urlString(documentPath: "/redirectmissing.html"));
1330 server.serveDirectory(dataDirectory());
1331
1332 QQmlComponent component(engine.get(), testFileUrl(fileName: "redirectError.qml"));
1333 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
1334 QVERIFY(!object.isNull());
1335 object->setProperty(name: "url", value: server.urlString(documentPath: "/redirect.html"));
1336 object->setProperty(name: "expectedText", value: "");
1337 component.completeCreate();
1338
1339 QTRY_VERIFY(object->property("done").toBool());
1340 QCOMPARE(object->property("dataOK").toBool(), true);
1341 }
1342
1343 {
1344 TestHTTPServer server;
1345 QVERIFY2(server.listen(), qPrintable(server.errorString()));
1346 server.addRedirect(filename: "redirect.html", redirectName: server.urlString(documentPath: "/redirect.html"));
1347 server.serveDirectory(dataDirectory());
1348
1349 QQmlComponent component(engine.get(), testFileUrl(fileName: "redirectRecur.qml"));
1350 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
1351 QVERIFY(!object.isNull());
1352 object->setProperty(name: "url", value: server.urlString(documentPath: "/redirect.html"));
1353 object->setProperty(name: "expectedText", value: "");
1354 component.completeCreate();
1355
1356 for (int ii = 0; ii < 60; ++ii) {
1357 if (object->property(name: "done").toBool()) break;
1358 QTest::qWait(ms: 50);
1359 }
1360 QVERIFY(object->property("done").toBool());
1361
1362 QCOMPARE(object->property("dataOK").toBool(), true);
1363 }
1364}
1365
1366void tst_qqmlxmlhttprequest::responseXML_invalid()
1367{
1368 QQmlComponent component(engine.get(), testFileUrl(fileName: "responseXML_invalid.qml"));
1369 QScopedPointer<QObject> object(component.create());
1370 QVERIFY(!object.isNull());
1371
1372 QTRY_VERIFY(object->property("dataOK").toBool());
1373
1374 QCOMPARE(object->property("xmlNull").toBool(), true);
1375}
1376
1377// Test the Document DOM element
1378void tst_qqmlxmlhttprequest::document()
1379{
1380 QQmlComponent component(engine.get(), testFileUrl(fileName: "document.qml"));
1381 QScopedPointer<QObject> object(component.create());
1382 QVERIFY(!object.isNull());
1383
1384 QTRY_VERIFY(object->property("dataOK").toBool());
1385
1386 QCOMPARE(object->property("xmlTest").toBool(), true);
1387}
1388
1389// Test the Element DOM element
1390void tst_qqmlxmlhttprequest::element()
1391{
1392 QQmlComponent component(engine.get(), testFileUrl(fileName: "element.qml"));
1393 QScopedPointer<QObject> object(component.create());
1394 QVERIFY(!object.isNull());
1395
1396 QTRY_VERIFY(object->property("dataOK").toBool());
1397
1398 QCOMPARE(object->property("xmlTest").toBool(), true);
1399}
1400
1401// Test the Attr DOM element
1402void tst_qqmlxmlhttprequest::attr()
1403{
1404 QQmlComponent component(engine.get(), testFileUrl(fileName: "attr.qml"));
1405 QScopedPointer<QObject> object(component.create());
1406 QVERIFY(!object.isNull());
1407
1408 QTRY_VERIFY(object->property("dataOK").toBool());
1409
1410 QCOMPARE(object->property("xmlTest").toBool(), true);
1411}
1412
1413// Test the Text DOM element
1414void tst_qqmlxmlhttprequest::text()
1415{
1416 QQmlComponent component(engine.get(), testFileUrl(fileName: "text.qml"));
1417 QScopedPointer<QObject> object(component.create());
1418 QVERIFY(!object.isNull());
1419
1420 QTRY_VERIFY(object->property("dataOK").toBool());
1421
1422 QCOMPARE(object->property("xmlTest").toBool(), true);
1423 QCOMPARE(object->property("status").toInt(), 200);
1424}
1425
1426// Test the CDataSection DOM element
1427void tst_qqmlxmlhttprequest::cdata()
1428{
1429 QQmlComponent component(engine.get(), testFileUrl(fileName: "cdata.qml"));
1430 QScopedPointer<QObject> object(component.create());
1431 QVERIFY(!object.isNull());
1432
1433 QTRY_VERIFY(object->property("dataOK").toBool());
1434
1435 QCOMPARE(object->property("xmlTest").toBool(), true);
1436 QCOMPARE(object->property("status").toInt(), 200);
1437}
1438
1439void tst_qqmlxmlhttprequest::noQmlContext()
1440{
1441 TestHTTPServer server;
1442 QVERIFY2(server.listen(), qPrintable(server.errorString()));
1443 QVERIFY(server.wait(testFileUrl("open_network.expect"),
1444 testFileUrl("open_network.reply"),
1445 testFileUrl("testdocument.html")));
1446 QUrl url = server.urlString(QStringLiteral("/testdocument.html"));
1447
1448 QQmlEngine engine;
1449
1450 QFile f(testFile(fileName: "noqmlcontext.js"));
1451 QVERIFY(f.open(QIODevice::ReadOnly));
1452 QString script = QString::fromUtf8(str: f.readAll());
1453 QJSValue testFunction = engine.evaluate(program: script);
1454 QVERIFY(testFunction.isCallable());
1455
1456 QJSValue resultCollector = engine.newObject();
1457
1458 testFunction.call(args: QJSValueList() << url.toString() << resultCollector);
1459
1460 QTRY_COMPARE(resultCollector.property("responseText").toString(), "QML Rocks!\n");
1461 }
1462
1463void tst_qqmlxmlhttprequest::stateChangeCallingContext()
1464{
1465#ifdef Q_OS_WIN
1466 QSKIP("QTBUG-26738");
1467#endif
1468
1469 // ensure that we don't crash by attempting to evaluate
1470 // without a valid calling context.
1471
1472 TestHTTPServer server;
1473 QVERIFY2(server.listen(), qPrintable(server.errorString()));
1474 server.serveDirectory(dataDirectory(), TestHTTPServer::Delay);
1475
1476 QQmlComponent component(engine.get(), testFileUrl(fileName: "stateChangeCallingContext.qml"));
1477 QScopedPointer<QObject> object(component.beginCreate(engine.get()->rootContext()));
1478 QVERIFY(!object.isNull());
1479 object->setProperty(name: "serverBaseUrl", value: server.baseUrl().toString());
1480 component.completeCreate();
1481 server.sendDelayedItem();
1482 QTRY_VERIFY(object->property("success").toBool());
1483}
1484
1485QTEST_MAIN(tst_qqmlxmlhttprequest)
1486
1487#include "tst_qqmlxmlhttprequest.moc"
1488

source code of qtdeclarative/tests/auto/qml/qqmlxmlhttprequest/tst_qqmlxmlhttprequest.cpp