From 3aa53b8bc383ebcdf8dc922b2670170ec012949f Mon Sep 17 00:00:00 2001 From: Matthew Vogt Date: Mon, 16 Jan 2012 11:05:23 +1000 Subject: [PATCH] Allow QML URLs to contain pre-encoded octets Use QUrl Tolerant parsing mode to permit user-supplied URLs to contain pre-encoded octets which are not mangled by string conversion. Task-number: QTBUG-22756 Change-Id: I4b160b04340b95221d1eb3336bda8c0b38d2e232 Reviewed-by: Michael Brasser --- src/declarative/qml/qdeclarativecompiler.cpp | 12 ++- src/declarative/qml/qdeclarativeproperty.cpp | 28 ++++-- src/declarative/qml/v4/qv4bindings.cpp | 12 ++- src/declarative/qml/v8/qv8sequencewrapper_p_p.h | 6 +- .../data/urlListProperty.qml | 41 ++++++++ .../qdeclarativeecmascript/data/urlProperty.2.qml | 10 ++ .../tst_qdeclarativeecmascript.cpp | 38 ++++++++ .../qdeclarativelanguage/data/assignBasicTypes.qml | 2 +- .../tst_qdeclarativelanguage.cpp | 4 +- .../tst_qdeclarativeproperty.cpp | 108 +++++++++++++++++++++ 10 files changed, 245 insertions(+), 16 deletions(-) create mode 100644 tests/auto/declarative/qdeclarativeecmascript/data/urlListProperty.qml create mode 100644 tests/auto/declarative/qdeclarativeecmascript/data/urlProperty.2.qml diff --git a/src/declarative/qml/qdeclarativecompiler.cpp b/src/declarative/qml/qdeclarativecompiler.cpp index 25013c9..a855063 100644 --- a/src/declarative/qml/qdeclarativecompiler.cpp +++ b/src/declarative/qml/qdeclarativecompiler.cpp @@ -404,6 +404,14 @@ bool QDeclarativeCompiler::testLiteralAssignment(QDeclarativeScript::Property *p return true; } +static QUrl urlFromUserString(const QString &data) +{ + QUrl u; + // Preserve any valid percent-encoded octets supplied by the source + u.setEncodedUrl(data.toUtf8(), QUrl::TolerantMode); + return u; +} + /*! Generate a store instruction for assigning literal \a v to property \a prop. @@ -511,7 +519,7 @@ void QDeclarativeCompiler::genLiteralAssignment(QDeclarativeScript::Property *pr { Instruction::StoreUrl instr; QString string = v->value.asString(); - QUrl u = string.isEmpty() ? QUrl() : output->url.resolved(QUrl(string)); + QUrl u = string.isEmpty() ? QUrl() : output->url.resolved(urlFromUserString(string)); instr.propertyIndex = prop->index; instr.value = output->indexForUrl(u); output->addInstruction(instr); @@ -720,7 +728,7 @@ void QDeclarativeCompiler::genLiteralAssignment(QDeclarativeScript::Property *pr } else if (type == qMetaTypeId >()) { Instruction::StoreUrlQList instr; QString string = v->value.asString(); - QUrl u = string.isEmpty() ? QUrl() : output->url.resolved(QUrl(string)); + QUrl u = string.isEmpty() ? QUrl() : output->url.resolved(urlFromUserString(string)); instr.propertyIndex = prop->index; instr.value = output->indexForUrl(u); output->addInstruction(instr); diff --git a/src/declarative/qml/qdeclarativeproperty.cpp b/src/declarative/qml/qdeclarativeproperty.cpp index b393b77..6cc55cc 100644 --- a/src/declarative/qml/qdeclarativeproperty.cpp +++ b/src/declarative/qml/qdeclarativeproperty.cpp @@ -1052,6 +1052,22 @@ QVariant QDeclarativePropertyPrivate::readValueProperty() } } +static QUrl urlFromUserString(const QByteArray &data) +{ + QUrl u; + if (!data.isEmpty()) + { + // Preserve any valid percent-encoded octets supplied by the source + u.setEncodedUrl(data, QUrl::TolerantMode); + } + return u; +} + +static QUrl urlFromUserString(const QString &data) +{ + return urlFromUserString(data.toUtf8()); +} + // helper function to allow assignment / binding to QList properties. static QVariant resolvedUrlSequence(const QVariant &value, QDeclarativeContextData *context) { @@ -1059,19 +1075,19 @@ static QVariant resolvedUrlSequence(const QVariant &value, QDeclarativeContextDa if (value.userType() == qMetaTypeId()) { urls.append(value.toUrl()); } else if (value.userType() == qMetaTypeId()) { - urls.append(QUrl(value.toString())); + urls.append(urlFromUserString(value.toString())); } else if (value.userType() == qMetaTypeId()) { - urls.append(QUrl(QString::fromUtf8(value.toByteArray()))); + urls.append(urlFromUserString(value.toByteArray())); } else if (value.userType() == qMetaTypeId >()) { urls = value.value >(); } else if (value.userType() == qMetaTypeId()) { QStringList urlStrings = value.value(); for (int i = 0; i < urlStrings.size(); ++i) - urls.append(QUrl(urlStrings.at(i))); + urls.append(urlFromUserString(urlStrings.at(i))); } else if (value.userType() == qMetaTypeId >()) { QList urlStrings = value.value >(); for (int i = 0; i < urlStrings.size(); ++i) - urls.append(QUrl(urlStrings.at(i))); + urls.append(urlFromUserString(urlStrings.at(i))); } // note: QList is not currently supported. QList resolvedUrls; @@ -1211,10 +1227,10 @@ bool QDeclarativePropertyPrivate::write(QObject *object, u = value.toUrl(); found = true; } else if (variantType == QVariant::ByteArray) { - u = QUrl(QString::fromUtf8(value.toByteArray())); + u = urlFromUserString(value.toByteArray()); found = true; } else if (variantType == QVariant::String) { - u = QUrl(value.toString()); + u = urlFromUserString(value.toString()); found = true; } diff --git a/src/declarative/qml/v4/qv4bindings.cpp b/src/declarative/qml/v4/qv4bindings.cpp index 5255db0..ba53a41 100644 --- a/src/declarative/qml/v4/qv4bindings.cpp +++ b/src/declarative/qml/v4/qv4bindings.cpp @@ -420,15 +420,16 @@ inline static QUrl toUrl(Register *reg, int type, QDeclarativeContextData *conte if (vt == QVariant::Url) { base = var->toUrl(); } else if (vt == QVariant::ByteArray) { - base = QUrl(QString::fromUtf8(var->toByteArray())); + // Preserve any valid percent-encoded octets supplied by the source + base.setEncodedUrl(var->toByteArray(), QUrl::TolerantMode); } else if (vt == QVariant::String) { - base = QUrl(var->toString()); + base.setEncodedUrl(var->toString().toUtf8(), QUrl::TolerantMode); } else { if (ok) *ok = false; return QUrl(); } } else if (type == QMetaType::QString) { - base = QUrl(*reg->getstringptr()); + base.setEncodedUrl(reg->getstringptr()->toUtf8(), QUrl::TolerantMode); } else { if (ok) *ok = false; return QUrl(); @@ -1012,7 +1013,10 @@ void QV4Bindings::run(int instrIndex, quint32 &executedBlocks, output.cleanupString(); MARK_CLEAN_REGISTER(instr->unaryop.output); } - new (output.geturlptr()) QUrl(tmp); + QUrl *urlPtr = output.geturlptr(); + new (urlPtr) QUrl(); + urlPtr->setEncodedUrl(tmp.toUtf8(), QUrl::TolerantMode); + URL_REGISTER(instr->unaryop.output); } } diff --git a/src/declarative/qml/v8/qv8sequencewrapper_p_p.h b/src/declarative/qml/v8/qv8sequencewrapper_p_p.h index c7a8ca4..8dc1b11 100644 --- a/src/declarative/qml/v8/qv8sequencewrapper_p_p.h +++ b/src/declarative/qml/v8/qv8sequencewrapper_p_p.h @@ -180,12 +180,14 @@ static QString convertQStringToString(QV8Engine *, const QString &v) static QUrl convertV8ValueToUrl(QV8Engine *e, v8::Handle v) { - return QUrl(e->toString(v->ToString())); + QUrl u; + u.setEncodedUrl(e->toString(v->ToString()).toUtf8(), QUrl::TolerantMode); + return u; } static v8::Handle convertUrlToV8Value(QV8Engine *e, const QUrl &v) { - return e->toString(v.toString()); + return e->toString(v.toEncoded()); } static QString convertUrlToString(QV8Engine *, const QUrl &v) diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/urlListProperty.qml b/tests/auto/declarative/qdeclarativeecmascript/data/urlListProperty.qml new file mode 100644 index 0000000..eeb0815 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/urlListProperty.qml @@ -0,0 +1,41 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + // single url assignment to url list property + MySequenceConversionObject { + id: msco1 + objectName: "msco1" + } + + // single url binding to url list property + MySequenceConversionObject { + id: msco2 + objectName: "msco2" + urlListProperty: "http://qt-project.org/?get%3cDATA%3e"; + } + + // multiple url assignment to url list property + MySequenceConversionObject { + id: msco3 + objectName: "msco3" + } + + // multiple url binding to url list property + MySequenceConversionObject { + id: msco4 + objectName: "msco4" + urlListProperty: [ + "http://qt-project.org/?get%3cDATA%3e", + "http://qt-project.org/?get%3cDATA%3e" + ]; + } + + Component.onCompleted: { + msco1.urlListProperty = "http://qt-project.org/?get%3cDATA%3e"; + msco3.urlListProperty = [ + "http://qt-project.org/?get%3cDATA%3e", + "http://qt-project.org/?get%3cDATA%3e" + ]; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/urlProperty.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/urlProperty.2.qml new file mode 100644 index 0000000..0e8bdae --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/urlProperty.2.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +MyQmlObject { + property bool result + stringProperty: "http://example.org" + urlProperty: stringProperty + "/?get%3cDATA%3e" + value: urlProperty == stringProperty + "/?get%3cDATA%3e" + result: urlProperty == urlProperty +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp index 5f1f44e..18c56af 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp @@ -213,6 +213,8 @@ private slots: void aliasToCompositeElement(); void realToInt(); void urlProperty(); + void urlPropertyWithEncoding(); + void urlListPropertyWithEncoding(); void dynamicString(); void include(); void signalHandlers(); @@ -5519,6 +5521,42 @@ void tst_qdeclarativeecmascript::urlProperty() } } +void tst_qdeclarativeecmascript::urlPropertyWithEncoding() +{ + { + QDeclarativeComponent component(&engine, testFileUrl("urlProperty.2.qml")); + MyQmlObject *object = qobject_cast(component.create()); + QVERIFY(object != 0); + object->setStringProperty("http://qt-project.org"); + QUrl encoded; + encoded.setEncodedUrl("http://qt-project.org/?get%3cDATA%3e", QUrl::TolerantMode); + QCOMPARE(object->urlProperty(), encoded); + QCOMPARE(object->value(), 0); // Interpreting URL as string yields canonicalised version + QCOMPARE(object->property("result").toBool(), true); + } +} + +void tst_qdeclarativeecmascript::urlListPropertyWithEncoding() +{ + { + QDeclarativeComponent component(&engine, testFileUrl("urlListProperty.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + MySequenceConversionObject *msco1 = object->findChild(QLatin1String("msco1")); + MySequenceConversionObject *msco2 = object->findChild(QLatin1String("msco2")); + MySequenceConversionObject *msco3 = object->findChild(QLatin1String("msco3")); + MySequenceConversionObject *msco4 = object->findChild(QLatin1String("msco4")); + QVERIFY(msco1 != 0 && msco2 != 0 && msco3 != 0 && msco4 != 0); + QUrl encoded; + encoded.setEncodedUrl("http://qt-project.org/?get%3cDATA%3e", QUrl::TolerantMode); + QCOMPARE(msco1->urlListProperty(), (QList() << encoded)); + QCOMPARE(msco2->urlListProperty(), (QList() << encoded)); + QCOMPARE(msco3->urlListProperty(), (QList() << encoded << encoded)); + QCOMPARE(msco4->urlListProperty(), (QList() << encoded << encoded)); + delete object; + } +} + void tst_qdeclarativeecmascript::dynamicString() { QDeclarativeComponent component(&engine, testFileUrl("dynamicString.qml")); diff --git a/tests/auto/declarative/qdeclarativelanguage/data/assignBasicTypes.qml b/tests/auto/declarative/qdeclarativelanguage/data/assignBasicTypes.qml index 2313499..28a3401 100644 --- a/tests/auto/declarative/qdeclarativelanguage/data/assignBasicTypes.qml +++ b/tests/auto/declarative/qdeclarativelanguage/data/assignBasicTypes.qml @@ -22,7 +22,7 @@ MyTypeObject { variantProperty: "Hello World!" vectorProperty: "10,1,2.2" vector4Property: "10,1,2.2,2.3" - urlProperty: "main.qml" + urlProperty: "main.qml?with%3cencoded%3edata" objectProperty: MyTypeObject { intProperty: 8 } } diff --git a/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp b/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp index 7af60e4..65b5f61 100644 --- a/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp +++ b/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp @@ -580,7 +580,9 @@ void tst_qdeclarativelanguage::assignBasicTypes() QCOMPARE(object->variantProperty(), QVariant("Hello World!")); QCOMPARE(object->vectorProperty(), QVector3D(10, 1, 2.2)); QCOMPARE(object->vector4Property(), QVector4D(10, 1, 2.2, 2.3)); - QCOMPARE(object->urlProperty(), component.url().resolved(QUrl("main.qml"))); + QUrl encoded; + encoded.setEncodedUrl("main.qml?with%3cencoded%3edata", QUrl::TolerantMode); + QCOMPARE(object->urlProperty(), component.url().resolved(encoded)); QVERIFY(object->objectProperty() != 0); MyTypeObject *child = qobject_cast(object->objectProperty()); QVERIFY(child != 0); diff --git a/tests/auto/declarative/qdeclarativeproperty/tst_qdeclarativeproperty.cpp b/tests/auto/declarative/qdeclarativeproperty/tst_qdeclarativeproperty.cpp index bf927a7..348c258 100644 --- a/tests/auto/declarative/qdeclarativeproperty/tst_qdeclarativeproperty.cpp +++ b/tests/auto/declarative/qdeclarativeproperty/tst_qdeclarativeproperty.cpp @@ -49,6 +49,7 @@ #include #include "../../shared/util.h" +#include class MyQmlObject : public QObject { Q_OBJECT @@ -120,6 +121,9 @@ private slots: //writeToReadOnly(); + void urlHandling_data(); + void urlHandling(); + // Bugs void crashOnValueProperty(); void aliasPropertyBindings(); @@ -1338,6 +1342,110 @@ void tst_qdeclarativeproperty::writeListToList() QCOMPARE(container->children()->size(), 1);*/ } +void tst_qdeclarativeproperty::urlHandling_data() +{ + QTest::addColumn("input"); + QTest::addColumn("scheme"); + QTest::addColumn("path"); + QTest::addColumn("encoded"); + + QTest::newRow("unspecifiedFile") + << QByteArray("main.qml") + << QString("") + << QString("main.qml") + << QByteArray("main.qml"); + + QTest::newRow("specifiedFile") + << QByteArray("file:///main.qml") + << QString("file") + << QString("/main.qml") + << QByteArray("file:///main.qml"); + + QTest::newRow("httpFile") + << QByteArray("http://www.example.com/main.qml") + << QString("http") + << QString("/main.qml") + << QByteArray("http://www.example.com/main.qml"); + + QTest::newRow("pathFile") + << QByteArray("http://www.example.com/resources/main.qml") + << QString("http") + << QString("/resources/main.qml") + << QByteArray("http://www.example.com/resources/main.qml"); + + QTest::newRow("encodableName") + << QByteArray("http://www.example.com/main file.qml") + << QString("http") + << QString("/main file.qml") + << QByteArray("http://www.example.com/main%20file.qml"); + + QTest::newRow("preencodedName") + << QByteArray("http://www.example.com/resources%7cmain%20file.qml") + << QString("http") + << QString("/resources|main file.qml") + << QByteArray("http://www.example.com/resources%7cmain%20file.qml"); + + QTest::newRow("encodableQuery") + << QByteArray("http://www.example.com/main.qml?type=text/qml&comment=now working?") + << QString("http") + << QString("/main.qml") + << QByteArray("http://www.example.com/main.qml?type=text/qml&comment=now%20working?"); + + QTest::newRow("preencodedQuery") + << QByteArray("http://www.example.com/main.qml?type=text%2fqml&comment=now working%3f") + << QString("http") + << QString("/main.qml") + << QByteArray("http://www.example.com/main.qml?type=text%2fqml&comment=now%20working%3f"); + + QTest::newRow("encodableFragment") + << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000|volume+50%") + << QString("http") + << QString("/main.qml") + << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000%7Cvolume+50%25"); + + QTest::newRow("preencodedFragment") + << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000%7cvolume%2b50%") + << QString("http") + << QString("/main.qml") + << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000%7cvolume%2b50%25"); +} + +void tst_qdeclarativeproperty::urlHandling() +{ + QFETCH(QByteArray, input); + QFETCH(QString, scheme); + QFETCH(QString, path); + QFETCH(QByteArray, encoded); + + QString inputString(QString::fromUtf8(input)); + + { + PropertyObject o; + QDeclarativeProperty p(&o, "url"); + + // Test url written as QByteArray + QCOMPARE(p.write(input), true); + QUrl byteArrayResult(o.url()); + + QCOMPARE(byteArrayResult.scheme(), scheme); + QCOMPARE(byteArrayResult.path(), path); + QCOMPARE(byteArrayResult.toEncoded(), encoded); + } + + { + PropertyObject o; + QDeclarativeProperty p(&o, "url"); + + // Test url written as QString + QCOMPARE(p.write(inputString), true); + QUrl stringResult(o.url()); + + QCOMPARE(stringResult.scheme(), scheme); + QCOMPARE(stringResult.path(), path); + QCOMPARE(stringResult.toEncoded(), encoded); + } +} + void tst_qdeclarativeproperty::crashOnValueProperty() { QDeclarativeEngine *engine = new QDeclarativeEngine; -- 2.7.4