diff options
author | Ulf Hermann <[email protected]> | 2024-12-10 13:31:44 +0100 |
---|---|---|
committer | Ulf Hermann <[email protected]> | 2024-12-20 09:44:16 +0100 |
commit | bac4723a30988c6b5b0430b8a394bbcf0b604db1 (patch) | |
tree | 6c99a25bcf940172a25635c4758dd522eb916666 | |
parent | 33c13102bf13d9e561266328b0b3246f2164791d (diff) |
QtQml: Allow basic name quoting in qmldir files
This adds support for quoted file names and for escaping quotes and
backslashes with '\'. It breaks support for file names that genuinely
start with a quote.
[ChangeLog][QtQml] You can now quote file names in qmldir files using
'"'. This is so that you can write file names with spaces in them. '\'
functions as an escape character, allowing you to escape quotes that are
part of file names and backslash itself.
Task-number: QTBUG-131916
Change-Id: Ia89e60153d3fafa273034f81da37166f30284c76
Reviewed-by: Fabian Kosmale <[email protected]>
-rw-r--r-- | src/qml/qmldirparser/qqmldirparser.cpp | 66 | ||||
-rw-r--r-- | src/qml/qmldirparser/qqmldirparser_p.h | 1 | ||||
-rw-r--r-- | tests/auto/qml/qqmldirparser/data/ends-in-quoted/qmldir | 11 | ||||
-rw-r--r-- | tests/auto/qml/qqmldirparser/data/escaped-end-of-file/qmldir | 11 | ||||
-rw-r--r-- | tests/auto/qml/qqmldirparser/data/invalid-escaped/qmldir | 11 | ||||
-rw-r--r-- | tests/auto/qml/qqmldirparser/data/line-break-in-quoted/qmldir | 12 | ||||
-rw-r--r-- | tests/auto/qml/qqmldirparser/data/quoted/qmldir | 11 | ||||
-rw-r--r-- | tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp | 70 |
8 files changed, 186 insertions, 7 deletions
diff --git a/src/qml/qmldirparser/qqmldirparser.cpp b/src/qml/qmldirparser/qqmldirparser.cpp index e6a5691d3d..6ac3cb997a 100644 --- a/src/qml/qmldirparser/qqmldirparser.cpp +++ b/src/qml/qmldirparser/qqmldirparser.cpp @@ -62,9 +62,59 @@ inline static void scanToEnd(const QChar *&ch) { ++ch; } -inline static void scanWord(const QChar *&ch) { +inline static QString scanWord(const QChar *&ch) { + const QChar *begin = ch; while (!ch->isSpace() && !ch->isNull()) ++ch; + return QString(begin, ch - begin); +} + +QString QQmlDirParser::scanQuotedWord(const QChar *&ch, quint16 lineNumber, quint16 columnNumber) +{ + Q_ASSERT(*ch == QLatin1Char('"')); + ++ch; + + QString result; + + const QChar *begin = ch; + while (*ch != QLatin1Char('"')) { + if (ch->isNull()) { + reportError(lineNumber, columnNumber, + QStringLiteral("file ends inside a quoted string")); + result.append(begin, ch - begin); + return result; + } + + if (*ch == QLatin1Char('\n') || *ch == QLatin1Char('\r')) { + reportError(lineNumber, columnNumber, + QStringLiteral("line breaks in quoted strings are not supported as they " + "are not portable between different operating systems")); + result.append(begin, ch - begin); + return result; + } + + if (*ch == QLatin1Char('\\')) { + result.append(begin, ch - begin); + ++ch; + ++columnNumber; + if (*ch != QLatin1Char('"') && *ch != QLatin1Char('\\')) { + reportError(lineNumber, columnNumber, + QStringLiteral("only '\"' and '\\' can be escaped")); + return result; + } + begin = ch; + } + + ++ch; + ++columnNumber; + } + + result.append(begin, ch - begin); + + Q_ASSERT(*ch == QLatin1Char('"')); + ++ch; + + return result; } /*! @@ -142,16 +192,18 @@ bool QQmlDirParser::parse(const QString &source) scanToEnd(ch); break; } - const QChar *start = ch; - scanWord(ch); - if (sectionCount < 4) { - sections[sectionCount++] = source.mid(start-source.constData(), ch-start); - } else { - reportError(lineNumber, start-lineStart, QLatin1String("unexpected token")); + + if (sectionCount >= 4) { + reportError(lineNumber, ch - lineStart, QLatin1String("unexpected token")); scanToEnd(ch); invalidLine = true; break; } + + sections[sectionCount++] = (*ch == QLatin1Char('"')) + ? scanQuotedWord(ch, lineNumber, ch - lineStart) + : scanWord(ch); + scanSpace(ch); } while (*ch != QLatin1Char('\n') && !ch->isNull()); diff --git a/src/qml/qmldirparser/qqmldirparser_p.h b/src/qml/qmldirparser/qqmldirparser_p.h index deef8f2dcf..b36f0894f8 100644 --- a/src/qml/qmldirparser/qqmldirparser_p.h +++ b/src/qml/qmldirparser/qqmldirparser_p.h @@ -140,6 +140,7 @@ public: private: bool maybeAddComponent(const QString &typeName, const QString &fileName, const QString &version, QHash<QString,Component> &hash, int lineNumber = -1, bool multi = true); void reportError(quint16 line, quint16 column, const QString &message); + QString scanQuotedWord(const QChar *&ch, quint16 lineNumber, quint16 columnNumber); private: QList<QQmlJS::DiagnosticMessage> _errors; diff --git a/tests/auto/qml/qqmldirparser/data/ends-in-quoted/qmldir b/tests/auto/qml/qqmldirparser/data/ends-in-quoted/qmldir new file mode 100644 index 0000000000..ef00260a1f --- /dev/null +++ b/tests/auto/qml/qqmldirparser/data/ends-in-quoted/qmldir @@ -0,0 +1,11 @@ +# "Comment" + +module ModuleNamespace +plugin PluginA "plugina.so" # More "comment" + +ComponentA 1.0 "componenta\"1_0.qml" +ScriptA 1.0 "scripta 1_0.js" + +# " +ComponentA 1.5 "componenta\\1_5.qml" +ComponentB 1.5 "componentb 1_5.qml
\ No newline at end of file diff --git a/tests/auto/qml/qqmldirparser/data/escaped-end-of-file/qmldir b/tests/auto/qml/qqmldirparser/data/escaped-end-of-file/qmldir new file mode 100644 index 0000000000..8d621d0c3f --- /dev/null +++ b/tests/auto/qml/qqmldirparser/data/escaped-end-of-file/qmldir @@ -0,0 +1,11 @@ +# "Comment" + +module ModuleNamespace +plugin PluginA "plugina.so" # More "comment" + +ComponentA 1.0 "componenta\"1_0.qml" +ScriptA 1.0 "scripta 1_0.js" + +# " +ComponentA 1.5 "componenta\\1_5.qml" +ComponentB 1.5 "componentb 1_5.qml\
\ No newline at end of file diff --git a/tests/auto/qml/qqmldirparser/data/invalid-escaped/qmldir b/tests/auto/qml/qqmldirparser/data/invalid-escaped/qmldir new file mode 100644 index 0000000000..c6f43b6d94 --- /dev/null +++ b/tests/auto/qml/qqmldirparser/data/invalid-escaped/qmldir @@ -0,0 +1,11 @@ +# "Comment" + +module ModuleNamespace +plugin PluginA "plugin\a.so" # More "comment" + +ComponentA 1.0 "componenta\"1_0.qml" +ScriptA 1.0 "scripta 1_0.js" + +# " +ComponentA 1.5 "componenta\\1_5.qml" +ComponentB 1.5 "componentb 1_5.qml" diff --git a/tests/auto/qml/qqmldirparser/data/line-break-in-quoted/qmldir b/tests/auto/qml/qqmldirparser/data/line-break-in-quoted/qmldir new file mode 100644 index 0000000000..90a3403e72 --- /dev/null +++ b/tests/auto/qml/qqmldirparser/data/line-break-in-quoted/qmldir @@ -0,0 +1,12 @@ +# "Comment" + +module ModuleNamespace +plugin PluginA "plugin +a.so" # More "comment" + +ComponentA 1.0 "componenta\"1_0.qml" +ScriptA 1.0 "scripta 1_0.js" + +# " +ComponentA 1.5 "componenta\\1_5.qml" +ComponentB 1.5 "componentb 1_5.qml" diff --git a/tests/auto/qml/qqmldirparser/data/quoted/qmldir b/tests/auto/qml/qqmldirparser/data/quoted/qmldir new file mode 100644 index 0000000000..9a49f84c76 --- /dev/null +++ b/tests/auto/qml/qqmldirparser/data/quoted/qmldir @@ -0,0 +1,11 @@ +# "Comment" + +module ModuleNamespace +plugin PluginA "plugina.so" # More "comment" + +ComponentA 1.0 "componenta\"1_0.qml" +ScriptA 1.0 "scripta 1_0.js" + +# " +ComponentA 1.5 "componenta\\1_5.qml" +ComponentB 1.5 "componentb 1_5.qml" diff --git a/tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp b/tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp index 4cbb7aaf47..ea4d8dfef2 100644 --- a/tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp +++ b/tests/auto/qml/qqmldirparser/tst_qqmldirparser.cpp @@ -335,6 +335,76 @@ void tst_qqmldirparser::parse_data() << QStringList() << false; + QTest::newRow("quoted") + << "quoted/qmldir" + << QString() + << QStringList() + << (QStringList() << "PluginA|plugina.so") + << QStringList() + << (QStringList() << "ComponentA|componenta\"1_0.qml|1|0|false" + << "ComponentA|componenta\\1_5.qml|1|5|false" + << "ComponentB|componentb 1_5.qml|1|5|false") + << (QStringList() << "ScriptA|scripta 1_0.js|1|0") + << QStringList() + << false; + + QTest::newRow("ends-in-quoted") + << "ends-in-quoted/qmldir" + << QString() + << (QStringList() << "qmldir:11:33: file ends inside a quoted string") + << (QStringList() << "PluginA|plugina.so") + << QStringList() + << (QStringList() << "ComponentA|componenta\"1_0.qml|1|0|false" + << "ComponentA|componenta\\1_5.qml|1|5|false" + << "ComponentB|componentb 1_5.qml|1|5|false") + << (QStringList() << "ScriptA|scripta 1_0.js|1|0") + << QStringList() + << false; + + QTest::newRow("invalid-escaped") + << "invalid-escaped/qmldir" + << QString() + << (QStringList() + << "qmldir:4:22: only '\"' and '\\' can be escaped" + << "qmldir:4: plugin directive requires one or two arguments, but 3 were provided") + << QStringList() + << QStringList() + << (QStringList() << "ComponentA|componenta\"1_0.qml|1|0|false" + << "ComponentA|componenta\\1_5.qml|1|5|false" + << "ComponentB|componentb 1_5.qml|1|5|false") + << (QStringList() << "ScriptA|scripta 1_0.js|1|0") + << QStringList() + << false; + + QTest::newRow("escaped-end-of-file") + << "escaped-end-of-file/qmldir" + << QString() + << (QStringList() << "qmldir:11:34: only '\"' and '\\' can be escaped") + << (QStringList() << "PluginA|plugina.so") + << QStringList() + << (QStringList() << "ComponentA|componenta\"1_0.qml|1|0|false" + << "ComponentA|componenta\\1_5.qml|1|5|false" + << "ComponentB|componentb 1_5.qml|1|5|false") + << (QStringList() << "ScriptA|scripta 1_0.js|1|0") + << QStringList() + << false; + + QTest::newRow("line-break-in-quoted") + << "line-break-in-quoted/qmldir" + << QString() + << (QStringList() + << "qmldir:4:21: line breaks in quoted strings are not supported as they are not " + "portable between different operating systems" + << "qmldir:5: a component declaration requires two or three arguments, but 1 were provided") + << (QStringList() << "PluginA|plugin") + << QStringList() + << (QStringList() << "ComponentA|componenta\"1_0.qml|1|0|false" + << "ComponentA|componenta\\1_5.qml|1|5|false" + << "ComponentB|componentb 1_5.qml|1|5|false") + << (QStringList() << "ScriptA|scripta 1_0.js|1|0") + << QStringList() + << false; + QTest::newRow("designersupported-yes") << "designersupported-yes/qmldir" << QString() |