From 2c4595ad02ff35011c24ea71c193d5a9710bec30 Mon Sep 17 00:00:00 2001 From: Matthew Vogt Date: Tue, 19 Jun 2012 10:24:01 +1000 Subject: [PATCH] Decode directory separators in source URLs URLs with encoded directory-separator characters are not correctly processed by QUrl. Task-number: QTBUG-25981 Change-Id: I78173ef44c4850774b56753335bea34db04c0735 Reviewed-by: Chris Adams --- src/qml/doc/src/typesystem/basictypes.qdoc | 15 +++++++++++++++ src/qml/qml/qqmlcompiler.cpp | 2 ++ src/qml/qml/qqmlproperty.cpp | 10 ++++++++-- src/qml/qml/v4/qv4bindings.cpp | 6 ++++-- tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp | 3 ++- tests/auto/quick/qquickloader/data/subdir/Test.qml | 5 +++++ tests/auto/quick/qquickloader/tst_qquickloader.cpp | 4 ++++ 7 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 tests/auto/quick/qquickloader/data/subdir/Test.qml diff --git a/src/qml/doc/src/typesystem/basictypes.qdoc b/src/qml/doc/src/typesystem/basictypes.qdoc index 3c1b849..c24787b 100644 --- a/src/qml/doc/src/typesystem/basictypes.qdoc +++ b/src/qml/doc/src/typesystem/basictypes.qdoc @@ -167,6 +167,21 @@ See \l {QML Basic Types} for the list of basic types that are supported by the Q Usually you will only have to do this once, because relative URLs resolved from that file will use the same protocol. + URLs may contain encoded characters using the 'percent-encoding' scheme + specified by \l {http://tools.ietf.org/html/rfc3986}{RFC 3986}. These characters + will be preserved within properties of type \c url, to allow QML code to + construct precise URL values. An exception to this rule is the preemptive + decoding of directory-separator characters (\c '/') - these characters are decoded + to allow the URL to be correctly classified. + + For example, a local file containing a '#' character, which would normally be + interpreted as the beginning of the URL 'fragment' element, can be accessed by + encoding the characters of the file name: + + \qml + Image { source: encodeURIComponent("/tmp/test#1.png") } + \endqml + \sa {QML Basic Types} */ diff --git a/src/qml/qml/qqmlcompiler.cpp b/src/qml/qml/qqmlcompiler.cpp index 2bb99dd..13ff9e8 100644 --- a/src/qml/qml/qqmlcompiler.cpp +++ b/src/qml/qml/qqmlcompiler.cpp @@ -515,6 +515,8 @@ void QQmlCompiler::genLiteralAssignment(QQmlScript::Property *prop, { Instruction::StoreUrl instr; QString string = v->value.asString(); + // Encoded dir-separators defeat QUrl processing - decode them first + string.replace(QLatin1String("%2f"), QLatin1String("/"), Qt::CaseInsensitive); QUrl u = string.isEmpty() ? QUrl() : output->url.resolved(QUrl(string)); instr.propertyIndex = prop->index; instr.value = output->indexForUrl(u); diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 33f860f..d0c2761 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -1319,10 +1319,16 @@ bool QQmlPropertyPrivate::write(QObject *object, u = value.toUrl(); found = true; } else if (variantType == QVariant::ByteArray) { - u = QUrl(QString::fromUtf8(value.toByteArray())); + QString input(QString::fromUtf8(value.toByteArray())); + // Encoded dir-separators defeat QUrl processing - decode them first + input.replace(QLatin1String("%2f"), QLatin1String("/"), Qt::CaseInsensitive); + u = QUrl(input); found = true; } else if (variantType == QVariant::String) { - u = QUrl(value.toString()); + QString input(value.toString()); + // Encoded dir-separators defeat QUrl processing - decode them first + input.replace(QLatin1String("%2f"), QLatin1String("/"), Qt::CaseInsensitive); + u = QUrl(input); found = true; } diff --git a/src/qml/qml/v4/qv4bindings.cpp b/src/qml/qml/v4/qv4bindings.cpp index ff16f3b..7d42b47 100644 --- a/src/qml/qml/v4/qv4bindings.cpp +++ b/src/qml/qml/v4/qv4bindings.cpp @@ -1296,7 +1296,9 @@ void QV4Bindings::run(int instrIndex, quint32 &executedBlocks, if (src.isUndefined()) { output.setUndefined(); } else { - const QString tmp(*src.getstringptr()); + QString tmp(*src.getstringptr()); + // Encoded dir-separators defeat QUrl processing - decode them first + tmp.replace(QLatin1String("%2f"), QLatin1String("/"), Qt::CaseInsensitive); if (instr->unaryop.src == instr->unaryop.output) { output.cleanupString(); MARK_CLEAN_REGISTER(instr->unaryop.output); @@ -1326,7 +1328,7 @@ void QV4Bindings::run(int instrIndex, quint32 &executedBlocks, COLOR_REGISTER(instr->unaryop.output); } } - QML_V4_END_INSTR(ConvertStringToUrl, unaryop) + QML_V4_END_INSTR(ConvertStringToColor, unaryop) QML_V4_BEGIN_INSTR(ConvertStringToVariant, unaryop) { diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index 248cc0d..658874f 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -1557,11 +1557,12 @@ void tst_qqmlproperty::urlHandling_data() << QString("/main.qml") << QByteArray("http://www.example.com/main.qml?type=text/qml&comment=now%20working?"); + // Although 'text%2Fqml' is pre-encoded, it will be decoded to allow correct QUrl classification 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"); + << QByteArray("http://www.example.com/main.qml?type=text/qml&comment=now%20working%3F"); QTest::newRow("encodableFragment") << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000|volume+50%") diff --git a/tests/auto/quick/qquickloader/data/subdir/Test.qml b/tests/auto/quick/qquickloader/data/subdir/Test.qml new file mode 100644 index 0000000..3abbd89 --- /dev/null +++ b/tests/auto/quick/qquickloader/data/subdir/Test.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + id: test +} diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index dfe02c6..dc21af8 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -201,6 +201,10 @@ void tst_QQuickLoader::sourceOrComponent_data() QTest::addColumn("errorString"); QTest::newRow("source") << "source" << "source: 'Rect120x60.qml'\n" << testFileUrl("Rect120x60.qml") << ""; + QTest::newRow("source with subdir") << "source" << "source: 'subdir/Test.qml'\n" << testFileUrl("subdir/Test.qml") << ""; + QTest::newRow("source with encoded subdir literal") << "source" << "source: 'subdir%2fTest.qml'\n" << testFileUrl("subdir/Test.qml") << ""; + QTest::newRow("source with encoded subdir optimized binding") << "source" << "source: 'subdir' + '%2fTest.qml'\n" << testFileUrl("subdir/Test.qml") << ""; + QTest::newRow("source with encoded subdir binding") << "source" << "source: encodeURIComponent('subdir/Test.qml')\n" << testFileUrl("subdir/Test.qml") << ""; QTest::newRow("sourceComponent") << "component" << "Component { id: comp; Rectangle { width: 100; height: 50 } }\n sourceComponent: comp\n" << QUrl() << ""; QTest::newRow("invalid source") << "source" << "source: 'IDontExist.qml'\n" << testFileUrl("IDontExist.qml") << QString(testFileUrl("IDontExist.qml").toString() + ": File not found"); -- 2.7.4