diff --git a/include/util/dutil.h b/include/util/dutil.h index 4353b854..7340dff4 100644 --- a/include/util/dutil.h +++ b/include/util/dutil.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2016 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2016 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -54,78 +54,13 @@ void SecureErase(T &obj) } } -inline QString escapeToObjectPath(const QString &str) -{ - if (str.isEmpty()) { - return "_"; - } +QString escapeToObjectPath(const QByteArray &str) noexcept; - auto ret = str; - QRegularExpression re{R"([^a-zA-Z0-9])"}; - auto matcher = re.globalMatch(ret); - while (matcher.hasNext()) { - auto replaceList = matcher.next().capturedTexts(); - replaceList.removeDuplicates(); - for (const auto &c : replaceList) { - auto hexStr = QString::number(static_cast(c.front().toLatin1()), 16); - ret.replace(c, QString{R"(_%1)"}.arg(hexStr)); - } - } - return ret; -} +QString escapeToObjectPath(const QString &str) noexcept; -inline QString unescapeFromObjectPath(const QString &str) -{ - auto ret = str; - for (int i = 0; i < str.size(); ++i) { - if (str[i] == '_' && i + 2 < str.size()) { - auto hexStr = str.mid(i + 1, 2); - ret.replace(QString{"_%1"}.arg(hexStr), QChar::fromLatin1(hexStr.toUInt(nullptr, 16))); - i += 2; - } - } - return ret; -} +QString unescapeFromObjectPath(const QString &str) noexcept; -inline QString getAppIdFromAbsolutePath(const QString &path) -{ - static QString desktopSuffix{u8".desktop"}; - const auto &appDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); - if (!path.endsWith(desktopSuffix) || - !std::any_of(appDirs.cbegin(), appDirs.constEnd(), [&path](const QString &dir) { return path.startsWith(dir); })) { - return {}; - } +QString getAppIdFromAbsolutePath(const QString &path) noexcept; - auto tmp = path.chopped(desktopSuffix.size()); - auto components = tmp.split(QDir::separator(), Qt::SkipEmptyParts); - auto location = std::find(components.cbegin(), components.cend(), "applications"); - if (location == components.cend()) { - return {}; - } - - auto appId = QStringList{location + 1, components.cend()}.join('-'); - return appId; -} - -inline QStringList getAbsolutePathFromAppId(const QString &appId) -{ - auto components = appId.split('-', Qt::SkipEmptyParts); - auto appDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); - - QStringList ret; - for (const auto &dirPath : appDirs) { - auto currentDir = dirPath; - for (auto it = components.cbegin(); it != components.cend(); ++it) { - auto currentName = QStringList{it, components.cend()}.join('-') + QString{".desktop"}; - QDir dir{currentDir}; - if (dir.exists(currentName)) { - ret.append(dir.filePath(currentName)); - } - - currentDir.append(QDir::separator() + *it); - } - } - - return ret; -} +QStringList getAbsolutePathFromAppId(const QString &appId) noexcept; } diff --git a/src/util/dutil.cpp b/src/util/dutil.cpp new file mode 100644 index 00000000..09a5769d --- /dev/null +++ b/src/util/dutil.cpp @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "dutil.h" + +namespace DUtil { + +QString escapeToObjectPath(const QByteArray &str) noexcept +{ + if (str.isEmpty()) { + return QStringLiteral("_"); + } + + QString ret; + ret.reserve(str.size() * 3); + + for (qsizetype i = 0; i < str.size(); ++i) { + auto byte = static_cast(str.at(i)); + if (std::isalnum(byte) != 0 || byte == '/') { + ret.append(QChar::fromLatin1(byte)); + } else { + // TODO: a valid dbus object path component only allows "[A-Z][a-z][0-9]_" + // for compatibility with existing applications, we escape all unicode to avoid breakage + // but we should consider to drop this compatibility hack in the future. + ret.append(u'_'); + ret.append(QString::number(byte, 16).rightJustified(2, u'0').toLower()); + } + } + + ret.shrink_to_fit(); + return ret; +} + +QString escapeToObjectPath(const QString &str) noexcept +{ + return escapeToObjectPath(str.toUtf8()); +} + +QString unescapeFromObjectPath(const QString &str) noexcept +{ + QByteArray ret; + ret.reserve(str.length()); + + for (qsizetype i = 0; i < str.length();) { + if (i <= str.length() - 3 && str.at(i) == u'_') { + bool ok{false}; + auto byte = static_cast(str.mid(i + 1, 2).toUShort(&ok, 16)); + if (ok) { + ret.append(static_cast(byte)); + i += 3; + continue; + } + } + + ret.append(str.at(i).toLatin1()); + ++i; + } + + return QString::fromUtf8(ret); +} + +QString getAppIdFromAbsolutePath(const QString &path) noexcept +{ + static QString desktopSuffix{u8".desktop"}; + const auto &appDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); + if (!path.endsWith(desktopSuffix) || + !std::any_of(appDirs.cbegin(), appDirs.constEnd(), [&path](const QString &dir) { return path.startsWith(dir); })) { + return {}; + } + + auto tmp = path.chopped(desktopSuffix.size()); + auto components = tmp.split(QDir::separator(), Qt::SkipEmptyParts); + auto location = std::find(components.cbegin(), components.cend(), "applications"); + if (location == components.cend()) { + return {}; + } + + auto appId = QStringList{location + 1, components.cend()}.join('-'); + return appId; +} + +QStringList getAbsolutePathFromAppId(const QString &appId) noexcept +{ + auto components = appId.split('-', Qt::SkipEmptyParts); + auto appDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); + + QStringList ret; + for (const auto &dirPath : appDirs) { + auto currentDir = dirPath; + for (auto it = components.cbegin(); it != components.cend(); ++it) { + auto currentName = QStringList{it, components.cend()}.join('-') + QString{".desktop"}; + QDir dir{currentDir}; + if (dir.exists(currentName)) { + ret.append(dir.filePath(currentName)); + } + + currentDir.append(QDir::separator() + *it); + } + } + + return ret; +} + +} diff --git a/src/util/util.cmake b/src/util/util.cmake index cda8f50b..ce351977 100644 --- a/src/util/util.cmake +++ b/src/util/util.cmake @@ -17,6 +17,7 @@ if(LINUX) ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedabstractinterface.cpp ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedpendingcallwatcher.cpp ${CMAKE_CURRENT_LIST_DIR}/dtextencoding.cpp + ${CMAKE_CURRENT_LIST_DIR}/dutil.cpp ) else() set(UTILS_SOURCES @@ -37,6 +38,7 @@ else() ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedabstractinterface.cpp ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedpendingcallwatcher.cpp ${CMAKE_CURRENT_LIST_DIR}/dtextencoding.cpp + ${CMAKE_CURRENT_LIST_DIR}/dutil.cpp ) endif() file(GLOB UTILS_HEADERS