From 4703edb841869901a0418cdc6b908ac21d71ed08 Mon Sep 17 00:00:00 2001 From: Mat Sutcliffe Date: Fri, 11 Feb 2022 20:13:41 +0000 Subject: [PATCH] Fix dbus assert when hostname contains non-Latin characters --- src/blackmisc/identifier.cpp | 10 ++- src/blackmisc/stringutils.cpp | 63 +++++++++++++++++++ src/blackmisc/stringutils.h | 6 ++ .../testidentifier/testidentifier.cpp | 2 +- .../testsharedstate/testsharedstate.cpp | 5 ++ 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/blackmisc/identifier.cpp b/src/blackmisc/identifier.cpp index ab3a0454e..f7b10ccee 100644 --- a/src/blackmisc/identifier.cpp +++ b/src/blackmisc/identifier.cpp @@ -21,27 +21,25 @@ BLACK_DEFINE_VALUEOBJECT_MIXINS(BlackMisc, CIdentifier) //! \private Escape characters not allowed in dbus paths QString toDBusPath(const QString &s) { - Q_ASSERT_X(!BlackMisc::containsChar(s, [](QChar c) { return c.unicode() > 0x7f; }), Q_FUNC_INFO, "7-bit ASCII only"); - return s.toLatin1().toPercentEncoding("/", "-._~", '_'); + return BlackMisc::utfToPercentEncoding(s, "/", '_'); } //! \private Escape characters not allowed in dbus path elements QString toDBusPathElement(const QString &s) { - Q_ASSERT_X(!BlackMisc::containsChar(s, [](QChar c) { return c.unicode() > 0x7f; }), Q_FUNC_INFO, "7-bit ASCII only"); - return s.toLatin1().toPercentEncoding({}, "-._~", '_'); + return BlackMisc::utfToPercentEncoding(s, {}, '_'); } //! \private Unescape characters not allowed in dbus paths QString fromDBusPath(const QString &s) { - return QByteArray::fromPercentEncoding(s.toLatin1(), '_'); + return BlackMisc::utfFromPercentEncoding(s.toLatin1(), '_'); } //! \private Unescape characters not allowed in dbus path elements QString fromDBusPathElement(const QString &s) { - return QByteArray::fromPercentEncoding(s.toLatin1(), '_'); + return BlackMisc::utfFromPercentEncoding(s.toLatin1(), '_'); } //! \private diff --git a/src/blackmisc/stringutils.cpp b/src/blackmisc/stringutils.cpp index e5429b840..a3b7132d0 100644 --- a/src/blackmisc/stringutils.cpp +++ b/src/blackmisc/stringutils.cpp @@ -34,6 +34,69 @@ namespace BlackMisc return splitString(s, [](QChar c) { return c == '\n' || c == '\r'; }); } + QByteArray utfToPercentEncoding(const QString& s, const QByteArray &allow, char percent) + { + QByteArray result; + for (const QChar &c : s) + { + if (const char latin = c.toLatin1()) + { + if ((latin >= 'a' && latin <= 'z') || (latin >= 'A' && latin <= 'Z') + || (latin >= '0' && latin <= '9') || allow.contains(latin)) + { + result += c; + } + else + { + result += percent; + if (latin < 0x10) { result += '0'; } + result += QByteArray::number(static_cast(latin), 16); + } + } + else + { + result += percent; + result += 'x'; + const ushort unicode = c.unicode(); + if (unicode < 0x0010) { result += '0'; } + if (unicode < 0x0100) { result += '0'; } + if (unicode < 0x1000) { result += '0'; } + result += QByteArray::number(unicode, 16); + } + } + return result; + } + + QString utfFromPercentEncoding(const QByteArray& ba, char percent) + { + QString result; + for (int i = 0; i < ba.size(); ++i) + { + if (ba[i] == percent) + { + ++i; + Q_ASSERT(i < ba.size()); + if (ba[i] == 'x') + { + ++i; + Q_ASSERT(i < ba.size()); + result += QChar(ba.mid(i, 4).toInt(nullptr, 16)); + i += 3; + } + else + { + result += static_cast(ba.mid(i, 2).toInt(nullptr, 16)); + ++i; + } + } + else + { + result += ba[i]; + } + } + return result; + } + const QString &boolToOnOff(bool v) { static const QString on("on"); diff --git a/src/blackmisc/stringutils.h b/src/blackmisc/stringutils.h index bd8599994..3927b6ecf 100644 --- a/src/blackmisc/stringutils.h +++ b/src/blackmisc/stringutils.h @@ -126,6 +126,12 @@ namespace BlackMisc //! Split a string into multiple lines. Blank lines are skipped. BLACKMISC_EXPORT QStringList splitLines(const QString &s); + //! Extended percent encoding supporting UTF-16 + BLACKMISC_EXPORT QByteArray utfToPercentEncoding(const QString &s, const QByteArray &allow = {}, char percent = '%'); + + //! Reverse utfFromPercentEncoding + BLACKMISC_EXPORT QString utfFromPercentEncoding(const QByteArray &ba, char percent = '%'); + //! A map converted to string template QString qmapToString(const QMap &map) { diff --git a/tests/blackmisc/testidentifier/testidentifier.cpp b/tests/blackmisc/testidentifier/testidentifier.cpp index c500b1424..dd85efd85 100644 --- a/tests/blackmisc/testidentifier/testidentifier.cpp +++ b/tests/blackmisc/testidentifier/testidentifier.cpp @@ -73,7 +73,7 @@ namespace BlackMiscTest void CTestIdentifier::dbusObjectPath() { QObject q; - q.setObjectName("!@#$%^&*()_+"); + q.setObjectName(QString::fromUtf16(u"!@#$%^&*()_+\u263a")); CTestIdentifiable id(&q); QString s(id.identifier().toDBusObjectPath()); QVERIFY2(id.identifier() == CIdentifier::fromDBusObjectPath(s), "Conversion from dbus object path and back compares equal"); diff --git a/tests/blackmisc/testsharedstate/testsharedstate.cpp b/tests/blackmisc/testsharedstate/testsharedstate.cpp index 2dc3154f7..a54271fe3 100644 --- a/tests/blackmisc/testsharedstate/testsharedstate.cpp +++ b/tests/blackmisc/testsharedstate/testsharedstate.cpp @@ -129,12 +129,17 @@ namespace BlackMiscTest //! ctor Server() { + QObject::connect(&m_process, qOverload(&QProcess::finished), [](int code, QProcess::ExitStatus status) + { + qDebug() << "Server process exited" << (status ? "abnormally" : "normally") << "with exit code" << code; + }); m_process.start("sharedstatetestserver", QStringList()); m_process.waitForStarted(); } //! dtor ~Server() { + m_process.disconnect(); m_process.kill(); m_process.waitForFinished(); }