diff options
| -rw-r--r-- | src/quick/util/qquicksvgparser.cpp | 46 | ||||
| -rw-r--r-- | src/quick/util/qquicksvgparser_p.h | 2 | ||||
| -rw-r--r-- | tests/auto/quick/qquickcanvasitem/data/tst_svgpath.qml | 43 | ||||
| -rw-r--r-- | tests/auto/quick/qquickpath/tst_qquickpath.cpp | 44 |
4 files changed, 113 insertions, 22 deletions
diff --git a/src/quick/util/qquicksvgparser.cpp b/src/quick/util/qquicksvgparser.cpp index 59a976b65a..84efe061ec 100644 --- a/src/quick/util/qquicksvgparser.cpp +++ b/src/quick/util/qquicksvgparser.cpp @@ -19,7 +19,7 @@ static inline bool isDigit(ushort ch) return ((ch >> 4) == 3) && (magic >> (ch & 15)); } -static qreal toDouble(const QChar *&str) +static qreal toDouble(const QChar *&str, const QChar *end) { const int maxLen = 255;//technically doubles can go til 308+ but whatever char temp[maxLen+1]; @@ -31,28 +31,28 @@ static qreal toDouble(const QChar *&str) } else if (*str == QLatin1Char('+')) { ++str; } - while (isDigit(str->unicode()) && pos < maxLen) { + while (str != end && isDigit(str->unicode()) && pos < maxLen) { temp[pos++] = str->toLatin1(); ++str; } - if (*str == QLatin1Char('.') && pos < maxLen) { + if (str != end && *str == QLatin1Char('.') && pos < maxLen) { temp[pos++] = '.'; ++str; } - while (isDigit(str->unicode()) && pos < maxLen) { + while (str != end && isDigit(str->unicode()) && pos < maxLen) { temp[pos++] = str->toLatin1(); ++str; } bool exponent = false; - if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) { + if (str != end && (*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) { exponent = true; temp[pos++] = 'e'; ++str; - if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { + if (str != end && (*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { temp[pos++] = str->toLatin1(); ++str; } - while (isDigit(str->unicode()) && pos < maxLen) { + while (str != end && isDigit(str->unicode()) && pos < maxLen) { temp[pos++] = str->toLatin1(); ++str; } @@ -96,24 +96,28 @@ static qreal toDouble(const QChar *&str) return val; } -static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points) +static inline void parseNumbersArray(const QChar *&str, const QChar *end, QVarLengthArray<qreal, 8> &points) { - while (str->isSpace()) - ++str; - while (isDigit(str->unicode()) || + auto eatWhitespace = [&]{ + while (str != end && str->isSpace()) + ++str; + return str != end; + }; + if (!eatWhitespace()) + return; + while (str != end && (isDigit(str->unicode()) || *str == QLatin1Char('-') || *str == QLatin1Char('+') || - *str == QLatin1Char('.')) { + *str == QLatin1Char('.'))) { - points.append(toDouble(str)); + points.append(toDouble(str, end)); - while (str->isSpace()) - ++str; + if (!eatWhitespace()) + return; if (*str == QLatin1Char(',')) ++str; - //eat the rest of space - while (str->isSpace()) - ++str; + if (!eatWhitespace()) + return; } } @@ -253,12 +257,12 @@ bool QQuickSvgParser::parsePathDataFast(const QString &dataStr, QPainterPath &pa const QChar *end = str + dataStr.size(); while (str != end) { - while (str->isSpace()) - ++str; QChar pathElem = *str; ++str; + if (pathElem.isSpace()) + continue; QVarLengthArray<qreal, 8> arg; - parseNumbersArray(str, arg); + parseNumbersArray(str, end, arg); if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) arg.append(0);//dummy const qreal *num = arg.constData(); diff --git a/src/quick/util/qquicksvgparser_p.h b/src/quick/util/qquicksvgparser_p.h index 3af0d182ae..4e358d9bee 100644 --- a/src/quick/util/qquicksvgparser_p.h +++ b/src/quick/util/qquicksvgparser_p.h @@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE namespace QQuickSvgParser { - bool parsePathDataFast(const QString &dataStr, QPainterPath &path); + Q_QUICK_AUTOTEST_EXPORT bool parsePathDataFast(const QString &dataStr, QPainterPath &path); Q_QUICK_EXPORT void pathArc(QPainterPath &path, qreal rx, qreal ry, qreal x_axis_rotation, int large_arc_flag, int sweep_flag, qreal x, qreal y, qreal curx, qreal cury); diff --git a/tests/auto/quick/qquickcanvasitem/data/tst_svgpath.qml b/tests/auto/quick/qquickcanvasitem/data/tst_svgpath.qml index 011fbd497b..09a6521ff6 100644 --- a/tests/auto/quick/qquickcanvasitem/data/tst_svgpath.qml +++ b/tests/auto/quick/qquickcanvasitem/data/tst_svgpath.qml @@ -56,4 +56,47 @@ CanvasTestCase { } } } + + // The test only verifies that assigning malformed strings (esp those ending + // with whitespace) to ctx.path does not crash the parser. + function test_svgpath_malformed_data() { + return [ + { tag: "empty", path: "" }, + { tag: "single space", path: " " }, + { tag: "multiple spaces", path: " " }, + { tag: "abs and newlines", path: "\t\n "}, + { tag: "command then trailing whitespace", path: "M0 0 "}, + { tag: "trailing whitespace after args", path: "M0 0 L10 10 \n "}, + { tag: "whitespace after command to end", path: "L \t"}, + { tag: "whitespace between command and numbers", path: "M 10 20 "}, + { tag: "trailing comma to end", path: "M0 0,"}, + { tag: "comma then whitespace to end", path: "M0 0, \n"}, + { tag: "comma between numbers then end", path: "L10,10, "}, + { tag: "integer to end", path: "M0 0 L10 20"}, + { tag: "decimal to end", path: "M0 0 L1.5 2.5"}, + { tag: "trailing dot to end", path: "M0 0 L1. 2."}, + { tag: "exponent to end", path: "M0 0 L1e2 3e2"}, + { tag: "signed exponent to end", path: "M0 0 L1e-2 3e-2"}, + { tag: "bare exponent letter to end", path: "M0 0 L1e"}, + { tag: "sign only token to end", path: "M0 0 L-"}, + { tag: "dot only token to end", path: "M0 0 L."} + ] + } + + function test_svgpath_malformed(data) { + var canvas = Qt.createQmlObject(` + import QtQuick + Canvas { + height: 100 + width:100 + renderTarget:Canvas.Image + } + `, testCase, "testCanvas"); + tryVerify(function() { return canvas.available; }); + var ctx = canvas.getContext('2d'); + ctx.beginPath(); + ctx.path = data.path; + ctx.fill(); + verify(true); // reached here without crashing + } } diff --git a/tests/auto/quick/qquickpath/tst_qquickpath.cpp b/tests/auto/quick/qquickpath/tst_qquickpath.cpp index 531a8a898b..89da273993 100644 --- a/tests/auto/quick/qquickpath/tst_qquickpath.cpp +++ b/tests/auto/quick/qquickpath/tst_qquickpath.cpp @@ -7,6 +7,7 @@ #include <QtQml/qqmlcomponent.h> #include <QtQuick/private/qquickpath_p.h> #include <QtQuick/private/qquickrectangle_p.h> +#include <QtQuick/private/qquicksvgparser_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> @@ -22,6 +23,8 @@ private slots: void catmullRomCurve(); void closedCatmullRomCurve(); void svg(); + void svgMalformed_data(); + void svgMalformed(); void line(); void rectangle_data(); void rectangle(); @@ -282,6 +285,47 @@ void tst_QuickPath::svg() svg(QSizeF(5,3)); } +void tst_QuickPath::svgMalformed_data() +{ + QTest::addColumn<QString>("svgPath"); + +#ifdef QT_BUILD_INTERNAL + // Malformed path strings must not crash the parser. + QTest::newRow("empty") << QString(); + QTest::newRow("single space") << QStringLiteral(" "); + QTest::newRow("multiple spaces") << QStringLiteral(" "); + QTest::newRow("tabs and newlines") << QStringLiteral("\t\n "); + QTest::newRow("command then trailing whitespace") << QStringLiteral("M0 0 "); + QTest::newRow("trailing whitespace after args") << QStringLiteral("M0 0 L10 10 \n "); + QTest::newRow("whitespace after command to end") << QStringLiteral("L \t"); + QTest::newRow("whitespace between command and numbers") << QStringLiteral("M 10 20 "); + QTest::newRow("trailing comma to end") << QStringLiteral("M0 0,"); + QTest::newRow("comma then whitespace to end") << QStringLiteral("M0 0, \n"); + QTest::newRow("comma between numbers then end") << QStringLiteral("L10,10, "); + QTest::newRow("integer to end") << QStringLiteral("M0 0 L10 20"); + QTest::newRow("decimal to end") << QStringLiteral("M0 0 L1.5 2.5"); + QTest::newRow("trailing dot to end") << QStringLiteral("M0 0 L1. 2."); + QTest::newRow("exponent to end") << QStringLiteral("M0 0 L1e2 3e2"); + QTest::newRow("signed exponent to end") << QStringLiteral("M0 0 L1e-2 3e-2"); + QTest::newRow("bare exponent letter to end") << QStringLiteral("M0 0 L1e"); + QTest::newRow("sign only token to end") << QStringLiteral("M0 0 L-"); + QTest::newRow("dot only token to end") << QStringLiteral("M0 0 L."); +#else + QSKIP("This test relies on private APIs that are only exported in developer-builds"); +#endif +} + +void tst_QuickPath::svgMalformed() +{ +#ifdef QT_BUILD_INTERNAL + QFETCH(QString, svgPath); + QPainterPath path; + QQuickSvgParser::parsePathDataFast(svgPath, path); +#else + QSKIP("This test relies on private APIs that are only exported in developer-builds"); +#endif +} + void tst_QuickPath::line(QSizeF scale) { QQmlEngine engine; |
