Allow QML URLs to contain pre-encoded octets
authorMatthew Vogt <matthew.vogt@nokia.com>
Mon, 16 Jan 2012 01:05:23 +0000 (11:05 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 18 Jan 2012 00:37:30 +0000 (01:37 +0100)
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 <michael.brasser@nokia.com>
src/declarative/qml/qdeclarativecompiler.cpp
src/declarative/qml/qdeclarativeproperty.cpp
src/declarative/qml/v4/qv4bindings.cpp
src/declarative/qml/v8/qv8sequencewrapper_p_p.h
tests/auto/declarative/qdeclarativeecmascript/data/urlListProperty.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/urlProperty.2.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp
tests/auto/declarative/qdeclarativelanguage/data/assignBasicTypes.qml
tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp
tests/auto/declarative/qdeclarativeproperty/tst_qdeclarativeproperty.cpp

index 25013c9..a855063 100644 (file)
@@ -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<QList<QUrl> >()) {
                 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);
index b393b77..6cc55cc 100644 (file)
@@ -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<QUrl> properties.
 static QVariant resolvedUrlSequence(const QVariant &value, QDeclarativeContextData *context)
 {
@@ -1059,19 +1075,19 @@ static QVariant resolvedUrlSequence(const QVariant &value, QDeclarativeContextDa
     if (value.userType() == qMetaTypeId<QUrl>()) {
         urls.append(value.toUrl());
     } else if (value.userType() == qMetaTypeId<QString>()) {
-        urls.append(QUrl(value.toString()));
+        urls.append(urlFromUserString(value.toString()));
     } else if (value.userType() == qMetaTypeId<QByteArray>()) {
-        urls.append(QUrl(QString::fromUtf8(value.toByteArray())));
+        urls.append(urlFromUserString(value.toByteArray()));
     } else if (value.userType() == qMetaTypeId<QList<QUrl> >()) {
         urls = value.value<QList<QUrl> >();
     } else if (value.userType() == qMetaTypeId<QStringList>()) {
         QStringList urlStrings = value.value<QStringList>();
         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<QString> >()) {
         QList<QString> urlStrings = value.value<QList<QString> >();
         for (int i = 0; i < urlStrings.size(); ++i)
-            urls.append(QUrl(urlStrings.at(i)));
+            urls.append(urlFromUserString(urlStrings.at(i)));
     } // note: QList<QByteArray> is not currently supported.
 
     QList<QUrl> 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;
         }
 
index 5255db0..ba53a41 100644 (file)
@@ -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);
         }
     }
index c7a8ca4..8dc1b11 100644 (file)
@@ -180,12 +180,14 @@ static QString convertQStringToString(QV8Engine *, const QString &v)
 
 static QUrl convertV8ValueToUrl(QV8Engine *e, v8::Handle<v8::Value> v)
 {
-    return QUrl(e->toString(v->ToString()));
+    QUrl u;
+    u.setEncodedUrl(e->toString(v->ToString()).toUtf8(), QUrl::TolerantMode);
+    return u;
 }
 
 static v8::Handle<v8::Value> 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 (file)
index 0000000..eeb0815
--- /dev/null
@@ -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 (file)
index 0000000..0e8bdae
--- /dev/null
@@ -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
+}
index 5f1f44e..18c56af 100644 (file)
@@ -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<MyQmlObject*>(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<MySequenceConversionObject *>(QLatin1String("msco1"));
+        MySequenceConversionObject *msco2 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco2"));
+        MySequenceConversionObject *msco3 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco3"));
+        MySequenceConversionObject *msco4 = object->findChild<MySequenceConversionObject *>(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<QUrl>() << encoded));
+        QCOMPARE(msco2->urlListProperty(), (QList<QUrl>() << encoded));
+        QCOMPARE(msco3->urlListProperty(), (QList<QUrl>() << encoded << encoded));
+        QCOMPARE(msco4->urlListProperty(), (QList<QUrl>() << encoded << encoded));
+        delete object;
+    }
+}
+
 void tst_qdeclarativeecmascript::dynamicString()
 {
     QDeclarativeComponent component(&engine, testFileUrl("dynamicString.qml"));
index 2313499..28a3401 100644 (file)
@@ -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 }
 }
index 7af60e4..65b5f61 100644 (file)
@@ -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<MyTypeObject *>(object->objectProperty());
     QVERIFY(child != 0);
index bf927a7..348c258 100644 (file)
@@ -49,6 +49,7 @@
 #include <QtCore/qdir.h>
 #include "../../shared/util.h"
 
+#include <QDebug>
 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<QByteArray>("input");
+    QTest::addColumn<QString>("scheme");
+    QTest::addColumn<QString>("path");
+    QTest::addColumn<QByteArray>("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;