Skip to content

Commit

Permalink
Allow QML URLs to contain pre-encoded octets
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Matthew Vogt authored and Qt by Nokia committed Jan 18, 2012
1 parent 52c1d7a commit 3aa53b8
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 16 deletions.
12 changes: 10 additions & 2 deletions src/declarative/qml/qdeclarativecompiler.cpp
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
28 changes: 22 additions & 6 deletions src/declarative/qml/qdeclarativeproperty.cpp
Expand Up @@ -1052,26 +1052,42 @@ 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)
{
QList<QUrl> urls;
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;
Expand Down Expand Up @@ -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;
}

Expand Down
12 changes: 8 additions & 4 deletions src/declarative/qml/v4/qv4bindings.cpp
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/declarative/qml/v8/qv8sequencewrapper_p_p.h
Expand Up @@ -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)
Expand Down
@@ -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"
];
}
}
@@ -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
}
Expand Up @@ -213,6 +213,8 @@ private slots:
void aliasToCompositeElement();
void realToInt();
void urlProperty();
void urlPropertyWithEncoding();
void urlListPropertyWithEncoding();
void dynamicString();
void include();
void signalHandlers();
Expand Down Expand Up @@ -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"));
Expand Down
Expand Up @@ -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 }
}
Expand Up @@ -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);
Expand Down
Expand Up @@ -49,6 +49,7 @@
#include <QtCore/qdir.h>
#include "../../shared/util.h"

#include <QDebug>
class MyQmlObject : public QObject
{
Q_OBJECT
Expand Down Expand Up @@ -120,6 +121,9 @@ private slots:

//writeToReadOnly();

void urlHandling_data();
void urlHandling();

// Bugs
void crashOnValueProperty();
void aliasPropertyBindings();
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 3aa53b8

Please sign in to comment.