diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a9e496..36d6643 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,103 +1,105 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.62.0") # handled by release scripts project(KConfig VERSION ${KF5_VERSION}) include(FeatureSummary) find_package(ECM 5.61.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(REQUIRED_QT_VERSION 5.11.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Xml) option(KCONFIG_USE_GUI "Build components using Qt5Gui" ON) if(KCONFIG_USE_GUI) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Gui) endif() if (NOT ANDROID) option(KCONFIG_USE_DBUS "Build components using Qt5DBus" ON) if(KCONFIG_USE_DBUS) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus) endif() else() set(KCONFIG_USE_DBUS Off) endif() include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMMarkNonGuiExecutable) include(ECMPoQmTools) include(ECMAddQch) include(ECMQtDeclareLoggingCategory) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX KCONFIG VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kconfig_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ConfigConfigVersion.cmake" SOVERSION 5) add_definitions(-DQT_NO_FOREACH) add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) endif() include (ECMPoQmTools) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ecm_install_po_files_as_qm(po) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Config") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5Config_QCH FILE KF5ConfigQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5ConfigQchTargets.cmake\")") endif() include(CMakePackageConfigHelpers) configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF5ConfigConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5ConfigConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5ConfigConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5ConfigConfigVersion.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/KF5ConfigMacros.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5ConfigTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5ConfigTargets.cmake NAMESPACE KF5:: COMPONENT Devel) install(EXPORT KF5ConfigCompilerTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5ConfigCompilerTargets.cmake NAMESPACE KF5:: COMPONENT Devel) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kconfig_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) + +install(FILES kconfig.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) diff --git a/kconfig.categories b/kconfig.categories new file mode 100644 index 0000000..2403da7 --- /dev/null +++ b/kconfig.categories @@ -0,0 +1 @@ +kf5.kconfig.core KConfig Core DEFAULT_SEVERITY [WARNING] IDENTIFIER [KCONFIG_CORE_LOG] diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 28aad4f..2fa8087 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,97 +1,102 @@ set(libkconfigcore_SRCS kconfig.cpp kconfigbase.cpp kconfigdata.cpp kconfiggroup.cpp kconfigbackend.cpp kconfigini.cpp kdesktopfile.cpp ksharedconfig.cpp kcoreconfigskeleton.cpp kauthorized.cpp kemailsettings.cpp kconfigwatcher.cpp ) +ecm_qt_declare_logging_category(libkconfigcore_SRCS + HEADER kconfig_core_log_settings.h + IDENTIFIER KCONFIG_CORE_LOG + CATEGORY_NAME kf5.kconfig.core) + configure_file(config-kconfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kconfig.h ) add_library(KF5ConfigCore ${libkconfigcore_SRCS}) generate_export_header(KF5ConfigCore BASE_NAME KConfigCore) add_library(KF5::ConfigCore ALIAS KF5ConfigCore) target_compile_definitions(KF5ConfigCore PRIVATE KCONF_UPDATE_INSTALL_LOCATION="${KDE_INSTALL_FULL_LIBEXECDIR_KF5}/$" ) target_include_directories(KF5ConfigCore INTERFACE "$") target_link_libraries(KF5ConfigCore PUBLIC Qt5::Core) if(KCONFIG_USE_DBUS) target_link_libraries(KF5ConfigCore PRIVATE Qt5::DBus) endif() if(WIN32) target_link_libraries(KF5ConfigCore PRIVATE ${KDEWIN_LIBRARIES}) endif() set_target_properties(KF5ConfigCore PROPERTIES VERSION ${KCONFIG_VERSION_STRING} SOVERSION ${KCONFIG_SOVERSION} EXPORT_NAME ConfigCore ) ecm_generate_headers(KConfigCore_HEADERS HEADER_NAMES KAuthorized KConfig KConfigBase KConfigGroup KDesktopFile KSharedConfig KCoreConfigSkeleton KEMailSettings ConversionCheck KConfigWatcher REQUIRED_HEADERS KConfigCore_HEADERS ) find_package(PythonModuleGeneration) if (PythonModuleGeneration_FOUND) ecm_generate_python_binding( TARGET KF5::ConfigCore PYTHONNAMESPACE PyKF5 MODULENAME KConfigCore RULES_FILE "${CMAKE_SOURCE_DIR}/cmake/rules_PyKF5.py" SIP_DEPENDS QtCore/QtCoremod.sip HEADERS kauthorized.h kconfig.h kconfigbase.h kconfiggroup.h kdesktopfile.h ksharedconfig.h kcoreconfigskeleton.h kemailsettings.h conversioncheck.h kconfigwatcher.h ) endif() install(TARGETS KF5ConfigCore EXPORT KF5ConfigTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kconfigcore_export.h ${KConfigCore_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KConfigCore COMPONENT Devel ) # make available to ecm_add_qch in parent folder set(KConfigCore_APIDOX_SRCS ${KConfigCore_HEADERS} PARENT_SCOPE) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KConfigCore LIB_NAME KF5ConfigCore DEPS "core" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KConfigCore) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/core/kconfig.cpp b/src/core/kconfig.cpp index f6824ce..9f29420 100644 --- a/src/core/kconfig.cpp +++ b/src/core/kconfig.cpp @@ -1,1020 +1,1021 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997-1999 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfig.h" #include "kconfig_p.h" #include "config-kconfig.h" +#include "kconfig_core_log_settings.h" #include #include #include "kconfigbackend_p.h" #include "kconfiggroup.h" #include #include #include #include #include #include #include #include #include #include #include #if KCONFIG_USE_DBUS #include #include #include #endif bool KConfigPrivate::mappingsRegistered = false; Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes. static QBasicMutex s_globalFilesMutex; Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals"))) #ifndef Q_OS_WIN static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive; #else static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive; #endif KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType) : openFlags(flags), resourceType(resourceType), mBackend(nullptr), bDynamicBackend(true), bDirty(false), bReadDefaults(false), bFileImmutable(false), bForceGlobal(false), bSuppressGlobal(false), configState(KConfigBase::NoAccess) { static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1); if (use_etc_kderc.load() < 0) { use_etc_kderc.store( !qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests } if (use_etc_kderc.load()) { etc_kderc = #ifdef Q_OS_WIN QFile::decodeName(qgetenv("WINDIR") + "/kde5rc"); #else QStringLiteral("/etc/kde5rc"); #endif if (!QFileInfo(etc_kderc).isReadable()) { use_etc_kderc.store(false); etc_kderc.clear(); } } // if (!mappingsRegistered) { // KEntryMap tmp; // if (!etc_kderc.isEmpty()) { // QExplicitlySharedDataPointer backend = KConfigBackend::create(etc_kderc, QLatin1String("INI")); // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults); // } // const QString kde5rc(QDir::home().filePath(".kde5rc")); // if (KStandardDirs::checkAccess(kde5rc, R_OK)) { // QExplicitlySharedDataPointer backend = KConfigBackend::create(kde5rc, QLatin1String("INI")); // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions()); // } // KConfigBackend::registerMappings(tmp); // mappingsRegistered = true; // } setLocale(QLocale().name()); } bool KConfigPrivate::lockLocal() { if (mBackend) { return mBackend->lock(); } // anonymous object - pretend we locked it return true; } void KConfigPrivate::copyGroup(const QByteArray &source, const QByteArray &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const { KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap; const int len = source.length(); const bool sameName = (destination == source); // we keep this bool outside the foreach loop so that if // the group is empty, we don't end up marking the other config // as dirty erroneously bool dirtied = false; for (KEntryMap::ConstIterator entryMapIt(entryMap.constBegin()); entryMapIt != entryMap.constEnd(); ++entryMapIt) { const QByteArray &group = entryMapIt.key().mGroup; if (!group.startsWith(source)) { // nothing to do continue; } // don't copy groups that start with the same prefix, but are not sub-groups if (group.length() > len && group[len] != '\x1d') { continue; } KEntryKey newKey = entryMapIt.key(); if (flags & KConfigBase::Localized) { newKey.bLocal = true; } if (!sameName) { newKey.mGroup.replace(0, len, destination); } KEntry entry = entryMap[ entryMapIt.key() ]; dirtied = entry.bDirty = flags & KConfigBase::Persistent; if (flags & KConfigBase::Global) { entry.bGlobal = true; } otherMap[newKey] = entry; } if (dirtied) { otherGroup->config()->d_ptr->bDirty = true; } } QString KConfigPrivate::expandString(const QString &value) { QString aValue = value; // check for environment variables and make necessary translations int nDollarPos = aValue.indexOf(QLatin1Char('$')); while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) { // there is at least one $ if (aValue[nDollarPos + 1] != QLatin1Char('$')) { int nEndPos = nDollarPos + 1; // the next character is not $ QStringRef aVarName; if (aValue[nEndPos] == QLatin1Char('{')) { while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) { nEndPos++; } nEndPos++; aVarName = aValue.midRef(nDollarPos + 2, nEndPos - nDollarPos - 3); } else { while (nEndPos <= aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) { nEndPos++; } aVarName = aValue.midRef(nDollarPos + 1, nEndPos - nDollarPos - 1); } QString env; if (!aVarName.isEmpty()) { #ifdef Q_OS_WIN if (aVarName == QLatin1String("HOME")) { env = QDir::homePath(); } else #endif { QByteArray pEnv = qgetenv(aVarName.toLatin1().constData()); if (!pEnv.isEmpty()) { env = QString::fromLocal8Bit(pEnv.constData()); } else { if (aVarName == QStringLiteral("QT_DATA_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); } else if (aVarName == QStringLiteral("QT_CONFIG_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); } else if (aVarName == QStringLiteral("QT_CACHE_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); } } } aValue.replace(nDollarPos, nEndPos - nDollarPos, env); nDollarPos += env.length(); } else { aValue.remove(nDollarPos, nEndPos - nDollarPos); } } else { // remove one of the dollar signs aValue.remove(nDollarPos, 1); nDollarPos++; } nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos); } return aValue; } KConfig::KConfig(const QString &file, OpenFlags mode, QStandardPaths::StandardLocation resourceType) : d_ptr(new KConfigPrivate(mode, resourceType)) { d_ptr->changeFileName(file); // set the local file name // read initial information off disk reparseConfiguration(); } KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType) : d_ptr(new KConfigPrivate(SimpleConfig, resourceType)) { d_ptr->mBackend = KConfigBackend::create(file, backend); d_ptr->bDynamicBackend = false; d_ptr->changeFileName(file); // set the local file name // read initial information off disk reparseConfiguration(); } KConfig::KConfig(KConfigPrivate &d) : d_ptr(&d) { } KConfig::~KConfig() { Q_D(KConfig); if (d->bDirty && (d->mBackend && d->mBackend->ref.load() == 1)) { sync(); } delete d; } QStringList KConfig::groupList() const { Q_D(const KConfig); QSet groups; for (KEntryMap::ConstIterator entryMapIt(d->entryMap.constBegin()); entryMapIt != d->entryMap.constEnd(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); const QByteArray group = key.mGroup; if (key.mKey.isNull() && !group.isEmpty() && group != "" && group != "$Version") { const QString groupname = QString::fromUtf8(group); groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); } } return groups.toList(); } QStringList KConfigPrivate::groupList(const QByteArray &group) const { QByteArray theGroup = group + '\x1d'; QSet groups; for (KEntryMap::ConstIterator entryMapIt(entryMap.constBegin()); entryMapIt != entryMap.constEnd(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); if (key.mKey.isNull() && key.mGroup.startsWith(theGroup)) { const QString groupname = QString::fromUtf8(key.mGroup.mid(theGroup.length())); groups << groupname.left(groupname.indexOf(QLatin1Char('\x1d'))); } } return groups.toList(); } static bool isGroupOrSubGroupMatch(const QByteArray &potentialGroup, const QByteArray &group) { if (!potentialGroup.startsWith(group)) { return false; } return potentialGroup.length() == group.length() || potentialGroup[group.length()] == '\x1d'; } // List all sub groups, including subsubgroups QSet KConfigPrivate::allSubGroups(const QByteArray &parentGroup) const { QSet groups; for (KEntryMap::const_iterator entryMapIt = entryMap.begin(); entryMapIt != entryMap.end(); ++entryMapIt) { const KEntryKey &key = entryMapIt.key(); if (key.mKey.isNull() && isGroupOrSubGroupMatch(key.mGroup, parentGroup)) { groups << key.mGroup; } } return groups; } bool KConfigPrivate::hasNonDeletedEntries(const QByteArray &group) const { for (KEntryMap::const_iterator it = entryMap.begin(); it != entryMap.end(); ++it) { const KEntryKey &key = it.key(); // Check for any non-deleted entry if (isGroupOrSubGroupMatch(key.mGroup, group) && !key.mKey.isNull() && !it->bDeleted) { return true; } } return false; } QStringList KConfigPrivate::keyListImpl(const QByteArray &theGroup) const { QStringList keys; const KEntryMapConstIterator theEnd = entryMap.constEnd(); KEntryMapConstIterator it = entryMap.findEntry(theGroup); if (it != theEnd) { ++it; // advance past the special group entry marker QSet tmp; for (; it != theEnd && it.key().mGroup == theGroup; ++it) { const KEntryKey &key = it.key(); if (!key.mKey.isNull() && !it->bDeleted) { tmp << QString::fromUtf8(key.mKey); } } keys = tmp.toList(); } return keys; } QStringList KConfig::keyList(const QString &aGroup) const { Q_D(const KConfig); const QByteArray theGroup(aGroup.isEmpty() ? "" : aGroup.toUtf8()); return d->keyListImpl(theGroup); } QMap KConfig::entryMap(const QString &aGroup) const { Q_D(const KConfig); QMap theMap; const QByteArray theGroup(aGroup.isEmpty() ? "" : aGroup.toUtf8()); const KEntryMapConstIterator theEnd = d->entryMap.constEnd(); KEntryMapConstIterator it = d->entryMap.findEntry(theGroup, nullptr, nullptr); if (it != theEnd) { ++it; // advance past the special group entry marker for (; it != theEnd && it.key().mGroup == theGroup; ++it) { // leave the default values and deleted entries out if (!it->bDeleted && !it.key().bDefault) { const QString key = QString::fromUtf8(it.key().mKey.constData()); // the localized entry should come first, so don't overwrite it // with the non-localized entry if (!theMap.contains(key)) { if (it->bExpand) { theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->mValue.constData()))); } else { theMap.insert(key, QString::fromUtf8(it->mValue.constData())); } } } } } return theMap; } bool KConfig::sync() { Q_D(KConfig); if (isImmutable() || name().isEmpty()) { // can't write to an immutable or anonymous file. return false; } QHash notifyGroupsLocal; QHash notifyGroupsGlobal; if (d->bDirty && d->mBackend) { const QByteArray utf8Locale(locale().toUtf8()); // Create the containing dir, maybe it wasn't there d->mBackend->createEnclosing(); // lock the local file if (d->configState == ReadWrite && !d->lockLocal()) { - qWarning() << "couldn't lock local file"; + qCWarning(KCONFIG_CORE_LOG) << "couldn't lock local file"; return false; } // Rewrite global/local config only if there is a dirty entry in it. bool writeGlobals = false; bool writeLocals = false; for (auto it = d->entryMap.constBegin(); it != d->entryMap.constEnd(); ++it) { auto e = it.value(); if (e.bDirty) { if (e.bGlobal) { writeGlobals = true; if (e.bNotify) { notifyGroupsGlobal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; } } else { writeLocals = true; if (e.bNotify) { notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey; } } } } d->bDirty = false; // will revert to true if a config write fails if (d->wantGlobals() && writeGlobals) { QExplicitlySharedDataPointer tmp = KConfigBackend::create(*sGlobalFileName); if (d->configState == ReadWrite && !tmp->lock()) { - qWarning() << "couldn't lock global file"; + qCWarning(KCONFIG_CORE_LOG) << "couldn't lock global file"; //unlock the local config if we're returning early if (d->mBackend->isLocked()) { d->mBackend->unlock(); } d->bDirty = true; return false; } if (!tmp->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteGlobal)) { d->bDirty = true; } if (tmp->isLocked()) { tmp->unlock(); } } if (writeLocals) { if (!d->mBackend->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteOptions())) { d->bDirty = true; } } if (d->mBackend->isLocked()) { d->mBackend->unlock(); } } if (!notifyGroupsLocal.isEmpty()) { d->notifyClients(notifyGroupsLocal, QStringLiteral("/") + name()); } if (!notifyGroupsGlobal.isEmpty()) { d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals")); } return !d->bDirty; } void KConfigPrivate::notifyClients(const QHash &changes, const QString &path) { #if KCONFIG_USE_DBUS qDBusRegisterMetaType(); qDBusRegisterMetaType>(); QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged")); message.setArguments({QVariant::fromValue(changes)}); QDBusConnection::sessionBus().send(message); #else Q_UNUSED(changes) Q_UNUSED(path) #endif } void KConfig::markAsClean() { Q_D(KConfig); d->bDirty = false; // clear any dirty flags that entries might have set const KEntryMapIterator theEnd = d->entryMap.end(); for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) { it->bDirty = false; it->bNotify = false; } } bool KConfig::isDirty() const { Q_D(const KConfig); return d->bDirty; } void KConfig::checkUpdate(const QString &id, const QString &updateFile) { const KConfigGroup cg(this, "$Version"); const QString cfg_id = updateFile + QLatin1Char(':') + id; const QStringList ids = cg.readEntry("update_info", QStringList()); if (!ids.contains(cfg_id)) { QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList() << QStringLiteral("--check") << updateFile); reparseConfiguration(); } } KConfig *KConfig::copyTo(const QString &file, KConfig *config) const { Q_D(const KConfig); if (!config) { config = new KConfig(QString(), SimpleConfig, d->resourceType); } config->d_func()->changeFileName(file); config->d_func()->entryMap = d->entryMap; config->d_func()->bFileImmutable = false; const KEntryMapIterator theEnd = config->d_func()->entryMap.end(); for (KEntryMapIterator it = config->d_func()->entryMap.begin(); it != theEnd; ++it) { it->bDirty = true; } config->d_ptr->bDirty = true; return config; } QString KConfig::name() const { Q_D(const KConfig); return d->fileName; } KConfig::OpenFlags KConfig::openFlags() const { Q_D(const KConfig); return d->openFlags; } struct KConfigStaticData { QString globalMainConfigName; // Keep a copy so we can use it in global dtors, after qApp is gone QStringList appArgs; }; Q_GLOBAL_STATIC(KConfigStaticData, globalData) void KConfig::setMainConfigName(const QString &str) { globalData()->globalMainConfigName = str; } QString KConfig::mainConfigName() { KConfigStaticData* data = globalData(); if (data->appArgs.isEmpty()) data->appArgs = QCoreApplication::arguments(); // --config on the command line overrides everything else const QStringList args = data->appArgs; for (int i = 1; i < args.count(); ++i) { if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) { return args.at(i + 1); } } const QString globalName = data->globalMainConfigName; if (!globalName.isEmpty()) { return globalName; } QString appName = QCoreApplication::applicationName(); return appName + QLatin1String("rc"); } void KConfigPrivate::changeFileName(const QString &name) { fileName = name; QString file; if (name.isEmpty()) { if (wantDefaults()) { // accessing default app-specific config "appnamerc" fileName = KConfig::mainConfigName(); file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName; } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere? resourceType = QStandardPaths::GenericConfigLocation; fileName = QStringLiteral("kdeglobals"); file = *sGlobalFileName; } else { // anonymous config openFlags = KConfig::SimpleConfig; return; } } else if (QDir::isAbsolutePath(fileName)) { fileName = QFileInfo(fileName).canonicalFilePath(); if (fileName.isEmpty()) { // file doesn't exist (yet) fileName = name; } file = fileName; } else { file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName; } Q_ASSERT(!file.isEmpty()); bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0); if (bDynamicBackend || !mBackend) { // allow dynamic changing of backend mBackend = KConfigBackend::create(file); } else { mBackend->setFilePath(file); } configState = mBackend->accessMode(); } void KConfig::reparseConfiguration() { Q_D(KConfig); if (d->fileName.isEmpty()) { return; } // Don't lose pending changes if (!d->isReadOnly() && d->bDirty) { sync(); } d->entryMap.clear(); d->bFileImmutable = false; { QMutexLocker locker(&s_globalFilesMutex); s_globalFiles()->clear(); } // Parse all desired files from the least to the most specific. if (d->wantGlobals()) { d->parseGlobalFiles(); } d->parseConfigFiles(); } QStringList KConfigPrivate::getGlobalFiles() const { QMutexLocker locker(&s_globalFilesMutex); if (s_globalFiles()->isEmpty()) { const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals")); const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals")); const bool useEtcKderc = !etc_kderc.isEmpty(); s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0)); for (const QString &dir1 : paths1) { s_globalFiles()->push_front(dir1); } for (const QString &dir2 : paths2) { s_globalFiles()->push_front(dir2); } if (useEtcKderc) { s_globalFiles()->push_front(etc_kderc); } } return *s_globalFiles(); } void KConfigPrivate::parseGlobalFiles() { const QStringList globalFiles = getGlobalFiles(); // qDebug() << "parsing global files" << globalFiles; // TODO: can we cache the values in etc_kderc / other global files // on a per-application basis? const QByteArray utf8Locale = locale.toUtf8(); for (const QString &file : globalFiles) { KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions; if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) parseOpts |= KConfigBackend::ParseDefaults; QExplicitlySharedDataPointer backend = KConfigBackend::create(file); if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable) { break; } } } void KConfigPrivate::parseConfigFiles() { // can only read the file if there is a backend and a file name if (mBackend && !fileName.isEmpty()) { bFileImmutable = false; QList files; if (wantDefaults()) { if (bSuppressGlobal) { files = getGlobalFiles(); } else { if (QDir::isAbsolutePath(fileName)) { const QString canonicalFile = QFileInfo(fileName).canonicalFilePath(); if (!canonicalFile.isEmpty()) { // empty if it doesn't exist files << canonicalFile; } } else { const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName); for (const QString &f : localFilesPath) { files.prepend(QFileInfo(f).canonicalFilePath()); } // allow fallback to config files bundled in resources const QString resourceFile(QStringLiteral(":/kconfig/") + fileName); if (QFile::exists(resourceFile)) { files.prepend(resourceFile); } } } } else { files << mBackend->filePath(); } if (!isSimple()) { files = extraFiles.toList() + files; } // qDebug() << "parsing local files" << files; const QByteArray utf8Locale = locale.toUtf8(); for (const QString &file : qAsConst(files)) { if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) { switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseExpansions)) { case KConfigBackend::ParseOk: break; case KConfigBackend::ParseImmutable: bFileImmutable = true; break; case KConfigBackend::ParseOpenError: configState = KConfigBase::NoAccess; break; } } else { QExplicitlySharedDataPointer backend = KConfigBackend::create(file); bFileImmutable = (backend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseDefaults | KConfigBackend::ParseExpansions) == KConfigBackend::ParseImmutable); } if (bFileImmutable) { break; } } } } KConfig::AccessMode KConfig::accessMode() const { Q_D(const KConfig); return d->configState; } void KConfig::addConfigSources(const QStringList &files) { Q_D(KConfig); for (const QString &file : files) { d->extraFiles.push(file); } if (!files.isEmpty()) { reparseConfiguration(); } } QStringList KConfig::additionalConfigSources() const { Q_D(const KConfig); return d->extraFiles.toList(); } QString KConfig::locale() const { Q_D(const KConfig); return d->locale; } bool KConfigPrivate::setLocale(const QString &aLocale) { if (aLocale != locale) { locale = aLocale; return true; } return false; } bool KConfig::setLocale(const QString &locale) { Q_D(KConfig); if (d->setLocale(locale)) { reparseConfiguration(); return true; } return false; } void KConfig::setReadDefaults(bool b) { Q_D(KConfig); d->bReadDefaults = b; } bool KConfig::readDefaults() const { Q_D(const KConfig); return d->bReadDefaults; } bool KConfig::isImmutable() const { Q_D(const KConfig); return d->bFileImmutable; } bool KConfig::isGroupImmutableImpl(const QByteArray &aGroup) const { Q_D(const KConfig); return isImmutable() || d->entryMap.getEntryOption(aGroup, nullptr, nullptr, KEntryMap::EntryImmutable); } #ifndef KDE_NO_DEPRECATED void KConfig::setForceGlobal(bool b) { Q_D(KConfig); d->bForceGlobal = b; } #endif #ifndef KDE_NO_DEPRECATED bool KConfig::forceGlobal() const { Q_D(const KConfig); return d->bForceGlobal; } #endif KConfigGroup KConfig::groupImpl(const QByteArray &group) { return KConfigGroup(this, group.constData()); } const KConfigGroup KConfig::groupImpl(const QByteArray &group) const { return KConfigGroup(this, group.constData()); } KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags) { KEntryMap::EntryOptions options = nullptr; if (flags & KConfig::Persistent) { options |= KEntryMap::EntryDirty; } if (flags & KConfig::Global) { options |= KEntryMap::EntryGlobal; } if (flags & KConfig::Localized) { options |= KEntryMap::EntryLocalized; } if (flags.testFlag(KConfig::Notify)) { options |= KEntryMap::EntryNotify; } return options; } void KConfig::deleteGroupImpl(const QByteArray &aGroup, WriteConfigFlags flags) { Q_D(KConfig); KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted; const QSet groups = d->allSubGroups(aGroup); for (const QByteArray &group : groups) { const QStringList keys = d->keyListImpl(group); for (const QString &_key : keys) { const QByteArray &key = _key.toUtf8(); if (d->canWriteEntry(group, key.constData())) { d->entryMap.setEntry(group, key, QByteArray(), options); d->bDirty = true; } } } } bool KConfig::isConfigWritable(bool warnUser) { Q_D(KConfig); bool allWritable = (d->mBackend ? d->mBackend->isWritable() : false); if (warnUser && !allWritable) { QString errorMsg; if (d->mBackend) { // TODO how can be it be null? Set errorMsg appropriately errorMsg = d->mBackend->nonWritableErrorMessage(); } // Note: We don't ask the user if we should not ask this question again because we can't save the answer. errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator."); QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog")); if (!cmdToExec.isEmpty()) { QProcess::execute(cmdToExec, QStringList() << QStringLiteral("--title") << QCoreApplication::applicationName() << QStringLiteral("--msgbox") << errorMsg); } } d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status return allWritable; } bool KConfig::hasGroupImpl(const QByteArray &aGroup) const { Q_D(const KConfig); // No need to look for the actual group entry anymore, or for subgroups: // a group exists if it contains any non-deleted entry. return d->hasNonDeletedEntries(aGroup); } bool KConfigPrivate::canWriteEntry(const QByteArray &group, const char *key, bool isDefault) const { if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) { return isDefault; } return true; } void KConfigPrivate::putData(const QByteArray &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand) { KEntryMap::EntryOptions options = convertToOptions(flags); if (bForceGlobal) { options |= KEntryMap::EntryGlobal; } if (expand) { options |= KEntryMap::EntryExpansion; } if (value.isNull()) { // deleting entry options |= KEntryMap::EntryDeleted; } bool dirtied = entryMap.setEntry(group, key, value, options); if (dirtied && (flags & KConfigBase::Persistent)) { bDirty = true; } } void KConfigPrivate::revertEntry(const QByteArray &group, const char *key, KConfigBase::WriteConfigFlags flags) { KEntryMap::EntryOptions options = convertToOptions(flags); bool dirtied = entryMap.revertEntry(group, key, options); if (dirtied) { bDirty = true; } } QByteArray KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const { if (bReadDefaults) { flags |= KEntryMap::SearchDefaults; } const KEntryMapConstIterator it = entryMap.findEntry(group, key, flags); if (it == entryMap.constEnd()) { return QByteArray(); } return it->mValue; } QString KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags, bool *expand) const { if (bReadDefaults) { flags |= KEntryMap::SearchDefaults; } return entryMap.getEntry(group, key, QString(), flags, expand); } QStandardPaths::StandardLocation KConfig::locationType() const { Q_D(const KConfig); return d->resourceType; } void KConfig::virtual_hook(int /*id*/, void * /*data*/) { /* nothing */ } diff --git a/src/core/kconfiggroup.cpp b/src/core/kconfiggroup.cpp index 92c58f6..c7b8cb6 100644 --- a/src/core/kconfiggroup.cpp +++ b/src/core/kconfiggroup.cpp @@ -1,1307 +1,1308 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfiggroup.h" #include "kconfiggroup_p.h" #include "kconfig.h" #include "kconfig_p.h" +#include "kconfig_core_log_settings.h" #include "ksharedconfig.h" #include "kconfigdata.h" #include #include #include #include #include #include #include #include #include #include class KConfigGroupPrivate : public QSharedData { public: KConfigGroupPrivate(KConfig *owner, bool isImmutable, bool isConst, const QByteArray &name) : mOwner(owner), mName(name), bImmutable(isImmutable), bConst(isConst) { } KConfigGroupPrivate(const KSharedConfigPtr &owner, const QByteArray &name) : sOwner(owner), mOwner(sOwner.data()), mName(name), bImmutable(name.isEmpty() ? owner->isImmutable() : owner->isGroupImmutable(name)), bConst(false) { } KConfigGroupPrivate(KConfigGroup *parent, bool isImmutable, bool isConst, const QByteArray &name) : sOwner(parent->d->sOwner), mOwner(parent->d->mOwner), mName(name), bImmutable(isImmutable), bConst(isConst) { if (!parent->d->mName.isEmpty()) { mParent = parent->d; } } KConfigGroupPrivate(const KConfigGroupPrivate *other, bool isImmutable, const QByteArray &name) : sOwner(other->sOwner), mOwner(other->mOwner), mName(name), bImmutable(isImmutable), bConst(other->bConst) { if (!other->mName.isEmpty()) { mParent = const_cast(other); } } KSharedConfig::Ptr sOwner; KConfig *mOwner; QExplicitlySharedDataPointer mParent; QByteArray mName; /* bitfield */ const bool bImmutable: 1; // is this group immutable? const bool bConst: 1; // is this group read-only? QByteArray fullName() const { if (!mParent) { return name(); } return mParent->fullName(mName); } QByteArray name() const { if (mName.isEmpty()) { return QByteArrayLiteral(""); } return mName; } QByteArray fullName(const QByteArray &aGroup) const { if (mName.isEmpty()) { return aGroup; } return fullName() + '\x1d' + aGroup; } static QExplicitlySharedDataPointer create(KConfigBase *master, const QByteArray &name, bool isImmutable, bool isConst) { QExplicitlySharedDataPointer data; if (dynamic_cast(master)) { data = new KConfigGroupPrivate(static_cast(master), isImmutable, isConst, name); } else { data = new KConfigGroupPrivate(dynamic_cast(master), isImmutable, isConst, name); } return data; } static QByteArray serializeList(const QList &list); static QStringList deserializeList(const QString &data); }; QByteArray KConfigGroupPrivate::serializeList(const QList &list) { QByteArray value; if (!list.isEmpty()) { QList::ConstIterator it = list.constBegin(); const QList::ConstIterator end = list.constEnd(); value = QByteArray(*it).replace('\\', QByteArrayLiteral("\\\\")).replace(',', QByteArrayLiteral("\\,")); while (++it != end) { // In the loop, so it is not done when there is only one element. // Doing it repeatedly is a pretty cheap operation. value.reserve(4096); value += ','; value += QByteArray(*it).replace('\\', QByteArrayLiteral("\\\\")).replace(',', QByteArrayLiteral("\\,")); } // To be able to distinguish an empty list from a list with one empty element. if (value.isEmpty()) { value = QByteArrayLiteral("\\0"); } } return value; } QStringList KConfigGroupPrivate::deserializeList(const QString &data) { if (data.isEmpty()) { return QStringList(); } if (data == QLatin1String("\\0")) { return QStringList(QString()); } QStringList value; QString val; val.reserve(data.size()); bool quoted = false; for (int p = 0; p < data.length(); p++) { if (quoted) { val += data[p]; quoted = false; } else if (data[p].unicode() == '\\') { quoted = true; } else if (data[p].unicode() == ',') { val.squeeze(); // release any unused memory value.append(val); val.clear(); val.reserve(data.size() - p); } else { val += data[p]; } } value.append(val); return value; } static QVector asIntList(const QByteArray &string) { const auto &splitString = string.split(','); QVector list; list.reserve(splitString.count()); for (const QByteArray &s : splitString) { list << s.toInt(); } return list; } static QVector asRealList(const QByteArray &string) { const auto &splitString = string.split(','); QVector list; list.reserve(splitString.count()); for (const QByteArray &s : splitString) { list << s.toDouble(); } return list; } static QString errString(const char *pKey, const QByteArray &value, const QVariant &aDefault) { return QStringLiteral("\"%1\" - conversion of \"%3\" to %2 failed") .arg( QString::fromLatin1(pKey), QString::fromLatin1(QVariant::typeToName(aDefault.type())), QString::fromLatin1(value) ); } static QString formatError(int expected, int got) { return QStringLiteral(" (wrong format: expected %1 items, got %2)").arg(expected).arg(got); } QVariant KConfigGroup::convertToQVariant(const char *pKey, const QByteArray &value, const QVariant &aDefault) { // if a type handler is added here you must add a QVConversions definition // to conversioncheck.h, or ConversionCheck::to_QVariant will not allow // readEntry to convert to QVariant. switch (static_cast(aDefault.type())) { case QMetaType::UnknownType: return QVariant(); case QMetaType::QString: // this should return the raw string not the dollar expanded string. // imho if processed string is wanted should call // readEntry(key, QString) not readEntry(key, QVariant) return QString::fromUtf8(value); case QMetaType::QVariantList: case QMetaType::QStringList: return KConfigGroupPrivate::deserializeList(QString::fromUtf8(value)); case QMetaType::QByteArray: return value; case QMetaType::Bool: { const QByteArray lower(value.toLower()); if (lower == "false" || lower == "no" || lower == "off" || lower == "0") { return false; } return true; } case QMetaType::Double: case QMetaType::Float: case QMetaType::Int: case QMetaType::UInt: case QMetaType::LongLong: case QMetaType::ULongLong: { QVariant tmp = value; if (!tmp.convert(aDefault.type())) { tmp = aDefault; } return tmp; } case QMetaType::QPoint: { const auto list = asIntList(value); if (list.count() != 2) { - qWarning() << errString(pKey, value, aDefault) + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(2, list.count()); return aDefault; } return QPoint(list.at(0), list.at(1)); } case QMetaType::QPointF: { const auto list = asRealList(value); if (list.count() != 2) { - qWarning() << errString(pKey, value, aDefault) + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(2, list.count()); return aDefault; } return QPointF(list.at(0), list.at(1)); } case QMetaType::QRect: { const auto list = asIntList(value); if (list.count() != 4) { - qWarning() << errString(pKey, value, aDefault) + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(4, list.count()); return aDefault; } const QRect rect(list.at(0), list.at(1), list.at(2), list.at(3)); if (!rect.isValid()) { - qWarning() << errString(pKey, value, aDefault); + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault); return aDefault; } return rect; } case QMetaType::QRectF: { const auto list = asRealList(value); if (list.count() != 4) { - qWarning() << errString(pKey, value, aDefault) + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(4, list.count()); return aDefault; } const QRectF rect(list.at(0), list.at(1), list.at(2), list.at(3)); if (!rect.isValid()) { - qWarning() << errString(pKey, value, aDefault); + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault); return aDefault; } return rect; } case QMetaType::QSize: { const auto list = asIntList(value); if (list.count() != 2) { - qWarning() << errString(pKey, value, aDefault) + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(2, list.count()); return aDefault; } const QSize size(list.at(0), list.at(1)); if (!size.isValid()) { - qWarning() << errString(pKey, value, aDefault); + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault); return aDefault; } return size; } case QMetaType::QSizeF: { const auto list = asRealList(value); if (list.count() != 2) { - qWarning() << errString(pKey, value, aDefault) + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(2, list.count()); return aDefault; } const QSizeF size(list.at(0), list.at(1)); if (!size.isValid()) { - qWarning() << errString(pKey, value, aDefault); + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault); return aDefault; } return size; } case QMetaType::QDateTime: { const auto list = asIntList(value); if (list.count() != 6) { - qWarning() << errString(pKey, value, aDefault) + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(6, list.count()); return aDefault; } const QDate date(list.at(0), list.at(1), list.at(2)); const QTime time(list.at(3), list.at(4), list.at(5)); const QDateTime dt(date, time); if (!dt.isValid()) { - qWarning() << errString(pKey, value, aDefault); + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault); return aDefault; } return dt; } case QMetaType::QDate: { auto list = asIntList(value); if (list.count() == 6) { list = list.mid(0, 3); // don't break config files that stored QDate as QDateTime } if (list.count() != 3) { - qWarning() << errString(pKey, value, aDefault) + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault) << formatError(3, list.count()); return aDefault; } const QDate date(list.at(0), list.at(1), list.at(2)); if (!date.isValid()) { - qWarning() << errString(pKey, value, aDefault); + qCWarning(KCONFIG_CORE_LOG) << errString(pKey, value, aDefault); return aDefault; } return date; } case QMetaType::QColor: case QMetaType::QFont: - qWarning() << "KConfigGroup::readEntry was passed GUI type '" + qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::readEntry was passed GUI type '" << aDefault.typeName() << "' but KConfigGui isn't linked! If it is linked to your program, " "this is a platform bug. Please inform the KDE developers"; break; case QMetaType::QUrl: return QUrl(QString::fromUtf8(value)); default: break; } - qWarning() << "unhandled type " << aDefault.typeName(); + qCWarning(KCONFIG_CORE_LOG) << "unhandled type " << aDefault.typeName(); return QVariant(); } #ifdef Q_OS_WIN # include #endif static bool cleanHomeDirPath(QString &path, const QString &homeDir) { #ifdef Q_OS_WIN //safer if (!QDir::toNativeSeparators(path).startsWith(QDir::toNativeSeparators(homeDir))) { return false; } #else if (!path.startsWith(homeDir)) { return false; } #endif int len = homeDir.length(); // replace by "$HOME" if possible if (len && (path.length() == len || path[len] == QLatin1Char('/'))) { path.replace(0, len, QStringLiteral("$HOME")); return true; } return false; } static QString translatePath(QString path) // krazy:exclude=passbyvalue { if (path.isEmpty()) { return path; } // only "our" $HOME should be interpreted path.replace(QLatin1Char('$'), QLatin1String("$$")); const bool startsWithFile = path.startsWith(QLatin1String("file:"), Qt::CaseInsensitive); path = startsWithFile ? QUrl(path).toLocalFile() : path; if (QDir::isRelativePath(path)) { return path; } // we can not use KGlobal::dirs()->relativeLocation("home", path) here, // since it would not recognize paths without a trailing '/'. // All of the 3 following functions to return the user's home directory // can return different paths. We have to test all them. const QString homeDir0 = QFile::decodeName(qgetenv("HOME")); const QString homeDir1 = QDir::homePath(); const QString homeDir2 = QDir(homeDir1).canonicalPath(); if (cleanHomeDirPath(path, homeDir0) || cleanHomeDirPath(path, homeDir1) || cleanHomeDirPath(path, homeDir2)) { // qDebug() << "Path was replaced\n"; } if (startsWithFile) { path = QUrl::fromLocalFile(path).toString(); } return path; } KConfigGroup::KConfigGroup() : d() { } bool KConfigGroup::isValid() const { return bool(d); } KConfigGroupGui _kde_internal_KConfigGroupGui; static inline bool readEntryGui(const QByteArray &data, const char *key, const QVariant &input, QVariant &output) { if (_kde_internal_KConfigGroupGui.readEntryGui) { return _kde_internal_KConfigGroupGui.readEntryGui(data, key, input, output); } return false; } static inline bool writeEntryGui(KConfigGroup *cg, const char *key, const QVariant &input, KConfigGroup::WriteConfigFlags flags) { if (_kde_internal_KConfigGroupGui.writeEntryGui) { return _kde_internal_KConfigGroupGui.writeEntryGui(cg, key, input, flags); } return false; } KConfigGroup::KConfigGroup(KConfigBase *master, const QString &_group) : d(KConfigGroupPrivate::create(master, _group.toUtf8(), master->isGroupImmutable(_group), false)) { } KConfigGroup::KConfigGroup(KConfigBase *master, const char *_group) : d(KConfigGroupPrivate::create(master, _group, master->isGroupImmutable(_group), false)) { } KConfigGroup::KConfigGroup(const KConfigBase *master, const QString &_group) : d(KConfigGroupPrivate::create(const_cast(master), _group.toUtf8(), master->isGroupImmutable(_group), true)) { } KConfigGroup::KConfigGroup(const KConfigBase *master, const char *_group) : d(KConfigGroupPrivate::create(const_cast(master), _group, master->isGroupImmutable(_group), true)) { } KConfigGroup::KConfigGroup(const KSharedConfigPtr &master, const QString &_group) : d(new KConfigGroupPrivate(master, _group.toUtf8())) { } KConfigGroup::KConfigGroup(const KSharedConfigPtr &master, const char *_group) : d(new KConfigGroupPrivate(master, _group)) { } KConfigGroup &KConfigGroup::operator=(const KConfigGroup &rhs) { d = rhs.d; return *this; } KConfigGroup::KConfigGroup(const KConfigGroup &rhs) : d(rhs.d) { } KConfigGroup::~KConfigGroup() { d.reset(); } KConfigGroup KConfigGroup::groupImpl(const QByteArray &aGroup) { Q_ASSERT_X(isValid(), "KConfigGroup::groupImpl", "accessing an invalid group"); Q_ASSERT_X(!aGroup.isEmpty(), "KConfigGroup::groupImpl", "can not have an unnamed child group"); KConfigGroup newGroup; newGroup.d = new KConfigGroupPrivate(this, isGroupImmutableImpl(aGroup), d->bConst, aGroup); return newGroup; } const KConfigGroup KConfigGroup::groupImpl(const QByteArray &aGroup) const { Q_ASSERT_X(isValid(), "KConfigGroup::groupImpl", "accessing an invalid group"); Q_ASSERT_X(!aGroup.isEmpty(), "KConfigGroup::groupImpl", "can not have an unnamed child group"); KConfigGroup newGroup; newGroup.d = new KConfigGroupPrivate(const_cast(this), isGroupImmutableImpl(aGroup), true, aGroup); return newGroup; } KConfigGroup KConfigGroup::parent() const { Q_ASSERT_X(isValid(), "KConfigGroup::parent", "accessing an invalid group"); KConfigGroup parentGroup; if (d->mParent) { parentGroup.d = d->mParent; } else { parentGroup.d = new KConfigGroupPrivate(d->mOwner, d->mOwner->isImmutable(), d->bConst, ""); // make sure we keep the refcount up on the KConfig object parentGroup.d->sOwner = d->sOwner; } return parentGroup; } void KConfigGroup::deleteGroup(WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroup", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroup", "deleting a read-only group"); config()->deleteGroup(d->fullName(), flags); } #ifndef KDE_NO_DEPRECATED void KConfigGroup::changeGroup(const QString &group) { Q_ASSERT_X(isValid(), "KConfigGroup::changeGroup", "accessing an invalid group"); d.detach(); d->mName = group.toUtf8(); } #endif #ifndef KDE_NO_DEPRECATED void KConfigGroup::changeGroup(const char *group) { Q_ASSERT_X(isValid(), "KConfigGroup::changeGroup", "accessing an invalid group"); d.detach(); d->mName = group; } #endif QString KConfigGroup::name() const { Q_ASSERT_X(isValid(), "KConfigGroup::name", "accessing an invalid group"); return QString::fromUtf8(d->name()); } bool KConfigGroup::exists() const { Q_ASSERT_X(isValid(), "KConfigGroup::exists", "accessing an invalid group"); return config()->hasGroup(d->fullName()); } bool KConfigGroup::sync() { Q_ASSERT_X(isValid(), "KConfigGroup::sync", "accessing an invalid group"); if (!d->bConst) { return config()->sync(); } return false; } QMap KConfigGroup::entryMap() const { Q_ASSERT_X(isValid(), "KConfigGroup::entryMap", "accessing an invalid group"); return config()->entryMap(QString::fromUtf8(d->fullName())); } KConfig *KConfigGroup::config() { Q_ASSERT_X(isValid(), "KConfigGroup::config", "accessing an invalid group"); return d->mOwner; } const KConfig *KConfigGroup::config() const { Q_ASSERT_X(isValid(), "KConfigGroup::config", "accessing an invalid group"); return d->mOwner; } bool KConfigGroup::isEntryImmutable(const char *key) const { Q_ASSERT_X(isValid(), "KConfigGroup::isEntryImmutable", "accessing an invalid group"); return (isImmutable() || !config()->d_func()->canWriteEntry(d->fullName(), key, config()->readDefaults())); } bool KConfigGroup::isEntryImmutable(const QString &key) const { return isEntryImmutable(key.toUtf8().constData()); } QString KConfigGroup::readEntryUntranslated(const QString &pKey, const QString &aDefault) const { return readEntryUntranslated(pKey.toUtf8().constData(), aDefault); } QString KConfigGroup::readEntryUntranslated(const char *key, const QString &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntryUntranslated", "accessing an invalid group"); QString result = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchFlags(), nullptr); if (result.isNull()) { return aDefault; } return result; } QString KConfigGroup::readEntry(const char *key, const char *aDefault) const { return readEntry(key, QString::fromUtf8(aDefault)); } QString KConfigGroup::readEntry(const QString &key, const char *aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QString KConfigGroup::readEntry(const char *key, const QString &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); bool expand = false; // read value from the entry map QString aValue = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchLocalized, &expand); if (aValue.isNull()) { aValue = aDefault; } if (expand) { return KConfigPrivate::expandString(aValue); } return aValue; } QString KConfigGroup::readEntry(const QString &key, const QString &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QStringList KConfigGroup::readEntry(const char *key, const QStringList &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); const QString data = readEntry(key, QString()); if (data.isNull()) { return aDefault; } return KConfigGroupPrivate::deserializeList(data); } QStringList KConfigGroup::readEntry(const QString &key, const QStringList &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QVariant KConfigGroup::readEntry(const char *key, const QVariant &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); const QByteArray data = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchLocalized); if (data.isNull()) { return aDefault; } QVariant value; if (!readEntryGui(data, key, aDefault, value)) { return convertToQVariant(key, data, aDefault); } return value; } QVariant KConfigGroup::readEntry(const QString &key, const QVariant &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QVariantList KConfigGroup::readEntry(const char *key, const QVariantList &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); const QString data = readEntry(key, QString()); if (data.isNull()) { return aDefault; } const auto &list = KConfigGroupPrivate::deserializeList(data); QVariantList value; value.reserve(list.count()); for (const QString &v : list) { value << v; } return value; } QVariantList KConfigGroup::readEntry(const QString &key, const QVariantList &aDefault) const { return readEntry(key.toUtf8().constData(), aDefault); } QStringList KConfigGroup::readXdgListEntry(const QString &key, const QStringList &aDefault) const { return readXdgListEntry(key.toUtf8().constData(), aDefault); } QStringList KConfigGroup::readXdgListEntry(const char *key, const QStringList &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readXdgListEntry", "accessing an invalid group"); const QString data = readEntry(key, QString()); if (data.isNull()) { return aDefault; } QStringList value; QString val; val.reserve(data.size()); // XXX List serialization being a separate layer from low-level parsing is // probably a bug. No affected entries are defined, though. bool quoted = false; for (int p = 0; p < data.length(); p++) { if (quoted) { val += data[p]; quoted = false; } else if (data[p] == QLatin1Char('\\')) { quoted = true; } else if (data[p] == QLatin1Char(';')) { value.append(val); val.clear(); val.reserve(data.size() - p); } else { val += data[p]; } } if (!val.isEmpty()) { value.append(val); } return value; } QString KConfigGroup::readPathEntry(const QString &pKey, const QString &aDefault) const { return readPathEntry(pKey.toUtf8().constData(), aDefault); } QString KConfigGroup::readPathEntry(const char *key, const QString &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readPathEntry", "accessing an invalid group"); bool expand = false; QString aValue = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchLocalized, &expand); if (aValue.isNull()) { aValue = aDefault; } return KConfigPrivate::expandString(aValue); } QStringList KConfigGroup::readPathEntry(const QString &pKey, const QStringList &aDefault) const { return readPathEntry(pKey.toUtf8().constData(), aDefault); } QStringList KConfigGroup::readPathEntry(const char *key, const QStringList &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readPathEntry", "accessing an invalid group"); const QString data = readPathEntry(key, QString()); if (data.isNull()) { return aDefault; } return KConfigGroupPrivate::deserializeList(data); } void KConfigGroup::writeEntry(const char *key, const QString &value, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); writeEntry(key, value.toUtf8(), flags); } void KConfigGroup::writeEntry(const QString &key, const QString &value, WriteConfigFlags flags) { writeEntry(key.toUtf8().constData(), value, flags); } void KConfigGroup::writeEntry(const QString &key, const char *value, WriteConfigFlags pFlags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); writeEntry(key.toUtf8().constData(), QVariant(QString::fromLatin1(value)), pFlags); } void KConfigGroup::writeEntry(const char *key, const char *value, WriteConfigFlags pFlags) { writeEntry(key, QVariant(QString::fromLatin1(value)), pFlags); } void KConfigGroup::writeEntry(const char *key, const QByteArray &value, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); config()->d_func()->putData(d->fullName(), key, value.isNull() ? QByteArray("") : value, flags); } void KConfigGroup::writeEntry(const QString &key, const QByteArray &value, WriteConfigFlags pFlags) { writeEntry(key.toUtf8().constData(), value, pFlags); } void KConfigGroup::writeEntry(const char *key, const QStringList &list, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); QList balist; balist.reserve(list.count()); for (const QString &entry : list) { balist.append(entry.toUtf8()); } writeEntry(key, KConfigGroupPrivate::serializeList(balist), flags); } void KConfigGroup::writeEntry(const QString &key, const QStringList &list, WriteConfigFlags flags) { writeEntry(key.toUtf8().constData(), list, flags); } void KConfigGroup::writeEntry(const char *key, const QVariantList &list, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); QList data; data.reserve(list.count()); for (const QVariant &v : list) { if (v.type() == QVariant::ByteArray) { data << v.toByteArray(); } else { data << v.toString().toUtf8(); } } writeEntry(key, KConfigGroupPrivate::serializeList(data), flags); } void KConfigGroup::writeEntry(const char *key, const QVariant &value, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeEntry", "writing to a read-only group"); if (writeEntryGui(this, key, value, flags)) { return; // GUI type that was handled } QByteArray data; // if a type handler is added here you must add a QVConversions definition // to conversioncheck.h, or ConversionCheck::to_QVariant will not allow // writeEntry to convert to QVariant. switch (static_cast(value.type())) { case QMetaType::UnknownType: data = ""; break; case QMetaType::QByteArray: data = value.toByteArray(); break; case QMetaType::QString: case QMetaType::Int: case QMetaType::UInt: case QMetaType::Double: case QMetaType::Float: case QMetaType::Bool: case QMetaType::LongLong: case QMetaType::ULongLong: data = value.toString().toUtf8(); break; case QMetaType::QVariantList: if (!value.canConvert(QMetaType::QStringList)) - qWarning() << "not all types in \"" << key << "\" can convert to QString," + qCWarning(KCONFIG_CORE_LOG) << "not all types in \"" << key << "\" can convert to QString," " information will be lost"; Q_FALLTHROUGH(); case QMetaType::QStringList: writeEntry(key, value.toList(), flags); return; case QMetaType::QPoint: { const QPoint rPoint = value.toPoint(); const QVariantList list{ rPoint.x(), rPoint.y() }; writeEntry(key, list, flags); return; } case QMetaType::QPointF: { const QPointF point = value.toPointF(); const QVariantList list{ point.x(), point.y() }; writeEntry(key, list, flags); return; } case QMetaType::QRect: { const QRect rRect = value.toRect(); const QVariantList list{ rRect.left(), rRect.top(), rRect.width(), rRect.height() }; writeEntry(key, list, flags); return; } case QMetaType::QRectF: { const QRectF rRectF = value.toRectF(); const QVariantList list{ rRectF.left(), rRectF.top(), rRectF.width(), rRectF.height() }; writeEntry(key, list, flags); return; } case QMetaType::QSize: { const QSize rSize = value.toSize(); const QVariantList list{ rSize.width(), rSize.height() }; writeEntry(key, list, flags); return; } case QMetaType::QSizeF: { const QSizeF rSizeF = value.toSizeF(); const QVariantList list{ rSizeF.width(), rSizeF.height() }; writeEntry(key, list, flags); return; } case QMetaType::QDate: { const QDate date = value.toDate(); const QVariantList list{ date.year(), date.month(), date.day() }; writeEntry(key, list, flags); return; } case QMetaType::QDateTime: { const QDateTime rDateTime = value.toDateTime(); const QTime time = rDateTime.time(); const QDate date = rDateTime.date(); const QVariantList list{ date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), }; writeEntry(key, list, flags); return; } case QMetaType::QColor: case QMetaType::QFont: - qWarning() << "KConfigGroup::writeEntry was passed GUI type '" + qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::writeEntry was passed GUI type '" << value.typeName() << "' but KConfigGui isn't linked! If it is linked to your program, this is a platform bug. " "Please inform the KDE developers"; break; case QMetaType::QUrl: data = QUrl(value.toUrl()).toString().toUtf8(); break; default: - qWarning() << "KConfigGroup::writeEntry - unhandled type" << value.typeName() << "in group" << name(); + qCWarning(KCONFIG_CORE_LOG) << "KConfigGroup::writeEntry - unhandled type" << value.typeName() << "in group" << name(); } writeEntry(key, data, flags); } void KConfigGroup::writeEntry(const QString &key, const QVariant &value, WriteConfigFlags flags) { writeEntry(key.toUtf8().constData(), value, flags); } void KConfigGroup::writeEntry(const QString &key, const QVariantList &list, WriteConfigFlags flags) { writeEntry(key.toUtf8().constData(), list, flags); } void KConfigGroup::writeXdgListEntry(const QString &key, const QStringList &value, WriteConfigFlags pFlags) { writeXdgListEntry(key.toUtf8().constData(), value, pFlags); } void KConfigGroup::writeXdgListEntry(const char *key, const QStringList &list, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::writeXdgListEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writeXdgListEntry", "writing to a read-only group"); QString value; value.reserve(4096); // XXX List serialization being a separate layer from low-level escaping is // probably a bug. No affected entries are defined, though. QStringList::ConstIterator it = list.constBegin(); const QStringList::ConstIterator end = list.constEnd(); for (; it != end; ++it) { QString val(*it); val.replace(QLatin1Char('\\'), QStringLiteral("\\\\")).replace(QLatin1Char(';'), QStringLiteral("\\;")); value += val; value += QLatin1Char(';'); } writeEntry(key, value, flags); } void KConfigGroup::writePathEntry(const QString &pKey, const QString &path, WriteConfigFlags pFlags) { writePathEntry(pKey.toUtf8().constData(), path, pFlags); } void KConfigGroup::writePathEntry(const char *pKey, const QString &path, WriteConfigFlags pFlags) { Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group"); config()->d_func()->putData(d->fullName(), pKey, translatePath(path).toUtf8(), pFlags, true); } void KConfigGroup::writePathEntry(const QString &pKey, const QStringList &value, WriteConfigFlags pFlags) { writePathEntry(pKey.toUtf8().constData(), value, pFlags); } void KConfigGroup::writePathEntry(const char *pKey, const QStringList &value, WriteConfigFlags pFlags) { Q_ASSERT_X(isValid(), "KConfigGroup::writePathEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::writePathEntry", "writing to a read-only group"); QList list; list.reserve(value.length()); for (const QString &path : value) { list << translatePath(path).toUtf8(); } config()->d_func()->putData(d->fullName(), pKey, KConfigGroupPrivate::serializeList(list), pFlags, true); } void KConfigGroup::deleteEntry(const char *key, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::deleteEntry", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteEntry", "deleting from a read-only group"); config()->d_func()->putData(d->fullName(), key, QByteArray(), flags); } void KConfigGroup::deleteEntry(const QString &key, WriteConfigFlags flags) { deleteEntry(key.toUtf8().constData(), flags); } void KConfigGroup::revertToDefault(const char *key) { revertToDefault(key, WriteConfigFlags()); } void KConfigGroup::revertToDefault(const char *key, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::revertToDefault", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::revertToDefault", "writing to a read-only group"); config()->d_func()->revertEntry(d->fullName(), key, flags); } void KConfigGroup::revertToDefault(const QString &key) { revertToDefault(key, WriteConfigFlags()); } void KConfigGroup::revertToDefault(const QString &key, WriteConfigFlags flags) { revertToDefault(key.toUtf8().constData(), flags); } bool KConfigGroup::hasDefault(const char *key) const { Q_ASSERT_X(isValid(), "KConfigGroup::hasDefault", "accessing an invalid group"); KEntryMap::SearchFlags flags = KEntryMap::SearchDefaults | KEntryMap::SearchLocalized; return !config()->d_func()->lookupData(d->fullName(), key, flags).isNull(); } bool KConfigGroup::hasDefault(const QString &key) const { return hasDefault(key.toUtf8().constData()); } bool KConfigGroup::hasKey(const char *key) const { Q_ASSERT_X(isValid(), "KConfigGroup::hasKey", "accessing an invalid group"); KEntryMap::SearchFlags flags = KEntryMap::SearchLocalized; if (config()->readDefaults()) { flags |= KEntryMap::SearchDefaults; } return !config()->d_func()->lookupData(d->fullName(), key, flags).isNull(); } bool KConfigGroup::hasKey(const QString &key) const { return hasKey(key.toUtf8().constData()); } bool KConfigGroup::isImmutable() const { Q_ASSERT_X(isValid(), "KConfigGroup::isImmutable", "accessing an invalid group"); return d->bImmutable; } QStringList KConfigGroup::groupList() const { Q_ASSERT_X(isValid(), "KConfigGroup::groupList", "accessing an invalid group"); return config()->d_func()->groupList(d->fullName()); } QStringList KConfigGroup::keyList() const { Q_ASSERT_X(isValid(), "KConfigGroup::keyList", "accessing an invalid group"); return entryMap().keys(); } void KConfigGroup::markAsClean() { Q_ASSERT_X(isValid(), "KConfigGroup::markAsClean", "accessing an invalid group"); config()->markAsClean(); } KConfigGroup::AccessMode KConfigGroup::accessMode() const { Q_ASSERT_X(isValid(), "KConfigGroup::accessMode", "accessing an invalid group"); return config()->accessMode(); } bool KConfigGroup::hasGroupImpl(const QByteArray &b) const { Q_ASSERT_X(isValid(), "KConfigGroup::hasGroupImpl", "accessing an invalid group"); return config()->hasGroup(d->fullName(b)); } void KConfigGroup::deleteGroupImpl(const QByteArray &b, WriteConfigFlags flags) { Q_ASSERT_X(isValid(), "KConfigGroup::deleteGroupImpl", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::deleteGroupImpl", "deleting from a read-only group"); config()->deleteGroup(d->fullName(b), flags); } bool KConfigGroup::isGroupImmutableImpl(const QByteArray &b) const { Q_ASSERT_X(isValid(), "KConfigGroup::isGroupImmutableImpl", "accessing an invalid group"); if (!hasGroupImpl(b)) { // group doesn't exist yet return d->bImmutable; // child groups are immutable if the parent is immutable. } return config()->isGroupImmutable(d->fullName(b)); } void KConfigGroup::copyTo(KConfigBase *other, WriteConfigFlags pFlags) const { Q_ASSERT_X(isValid(), "KConfigGroup::copyTo", "accessing an invalid group"); Q_ASSERT(other != nullptr); if (KConfigGroup *otherGroup = dynamic_cast(other)) { config()->d_func()->copyGroup(d->fullName(), otherGroup->d->fullName(), otherGroup, pFlags); } else if (KConfig *otherConfig = dynamic_cast(other)) { KConfigGroup newGroup = otherConfig->group(d->fullName()); otherConfig->d_func()->copyGroup(d->fullName(), d->fullName(), &newGroup, pFlags); } else { Q_ASSERT_X(false, "KConfigGroup::copyTo", "unknown type of KConfigBase"); } } void KConfigGroup::reparent(KConfigBase *parent, WriteConfigFlags pFlags) { Q_ASSERT_X(isValid(), "KConfigGroup::reparent", "accessing an invalid group"); Q_ASSERT_X(!d->bConst, "KConfigGroup::reparent", "reparenting a read-only group"); Q_ASSERT_X(!d->bImmutable, "KConfigGroup::reparent", "reparenting an immutable group"); Q_ASSERT(parent != nullptr); KConfigGroup oldGroup(*this); d = KConfigGroupPrivate::create(parent, d->mName, false, false); oldGroup.copyTo(this, pFlags); oldGroup.deleteGroup(); // so that the entries with the old group name are deleted on sync } diff --git a/src/core/kconfigini.cpp b/src/core/kconfigini.cpp index af30650..d07b59c 100644 --- a/src/core/kconfigini.cpp +++ b/src/core/kconfigini.cpp @@ -1,932 +1,933 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (C) 1997-1999 Matthias Kalle Dalheimer (kalle@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfigini_p.h" #include "kconfig.h" #include "kconfigbackend_p.h" #include "bufferfragment_p.h" #include "kconfigdata.h" +#include "kconfig_core_log_settings.h" #include #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include // getuid, close #endif #include // uid_t #include // open KCONFIGCORE_EXPORT bool kde_kiosk_exception = false; // flag to disable kiosk restrictions static QByteArray lookup(const KConfigIniBackend::BufferFragment fragment, QHash *cache) { auto it = cache->constFind(fragment); if (it != cache->constEnd()) { return it.value(); } return cache->insert(fragment, fragment.toByteArray()).value(); } QString KConfigIniBackend::warningProlog(const QFile &file, int line) { return QStringLiteral("KConfigIni: In file %2, line %1: ") .arg(line).arg(file.fileName()); } KConfigIniBackend::KConfigIniBackend() : KConfigBackend(), lockFile(nullptr) { } KConfigIniBackend::~KConfigIniBackend() { } KConfigBackend::ParseInfo KConfigIniBackend::parseConfig(const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options) { return parseConfig(currentLocale, entryMap, options, false); } // merging==true is the merging that happens at the beginning of writeConfig: // merge changes in the on-disk file with the changes in the KConfig object. KConfigBackend::ParseInfo KConfigIniBackend::parseConfig(const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options, bool merging) { if (filePath().isEmpty() || !QFile::exists(filePath())) { return ParseOk; } const QByteArray currentLanguage = currentLocale.split('_').first(); bool bDefault = options & ParseDefaults; bool allowExecutableValues = options & ParseExpansions; QByteArray currentGroup(""); QFile file(filePath()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return ParseOpenError; } QList immutableGroups; bool fileOptionImmutable = false; bool groupOptionImmutable = false; bool groupSkip = false; int lineNo = 0; // on systems using \r\n as end of line, \r will be taken care of by // trim() below QByteArray buffer = file.readAll(); BufferFragment contents(buffer.data(), buffer.size()); unsigned int len = contents.length(); unsigned int startOfLine = 0; // Reduce memory overhead by making use of implicit sharing // This assumes that config files contain only a small amount of // different fragments which are repeated often. // This is often the case, especially sub groups will all have // the same list of keys and similar values as well. QHash cache; cache.reserve(4096); while (startOfLine < len) { BufferFragment line = contents.split('\n', &startOfLine); line.trim(); lineNo++; // skip empty lines and lines beginning with '#' if (line.isEmpty() || line.at(0) == '#') { continue; } if (line.at(0) == '[') { // found a group groupOptionImmutable = fileOptionImmutable; QByteArray newGroup; int start = 1, end; do { end = start; for (;;) { if (end == line.length()) { - qWarning() << warningProlog(file, lineNo) << "Invalid group header."; + qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid group header."; // XXX maybe reset the current group here? goto next_line; } if (line.at(end) == ']') { break; } end++; } if (end + 1 == line.length() && start + 2 == end && line.at(start) == '$' && line.at(start + 1) == 'i') { if (newGroup.isEmpty()) { fileOptionImmutable = !kde_kiosk_exception; } else { groupOptionImmutable = !kde_kiosk_exception; } } else { if (!newGroup.isEmpty()) { newGroup += '\x1d'; } BufferFragment namePart = line.mid(start, end - start); printableToString(&namePart, file, lineNo); newGroup += namePart.toByteArray(); } } while ((start = end + 2) <= line.length() && line.at(end + 1) == '['); currentGroup = newGroup; groupSkip = entryMap.getEntryOption(currentGroup, nullptr, nullptr, KEntryMap::EntryImmutable); if (groupSkip && !bDefault) { continue; } if (groupOptionImmutable) // Do not make the groups immutable until the entries from // this file have been added. { immutableGroups.append(currentGroup); } } else { if (groupSkip && !bDefault) { continue; // skip entry } BufferFragment aKey; int eqpos = line.indexOf('='); if (eqpos < 0) { aKey = line; line.clear(); } else { BufferFragment temp = line.left(eqpos); temp.trim(); aKey = temp; line.truncateLeft(eqpos + 1); line.trim(); } if (aKey.isEmpty()) { - qWarning() << warningProlog(file, lineNo) << "Invalid entry (empty key)"; + qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (empty key)"; continue; } KEntryMap::EntryOptions entryOptions = nullptr; if (groupOptionImmutable) { entryOptions |= KEntryMap::EntryImmutable; } BufferFragment locale; int start; while ((start = aKey.lastIndexOf('[')) >= 0) { int end = aKey.indexOf(']', start); if (end < 0) { - qWarning() << warningProlog(file, lineNo) + qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (missing ']')"; goto next_line; } else if (end > start + 1 && aKey.at(start + 1) == '$') { // found option(s) int i = start + 2; while (i < end) { switch (aKey.at(i)) { case 'i': if (!kde_kiosk_exception) { entryOptions |= KEntryMap::EntryImmutable; } break; case 'e': if (allowExecutableValues) { entryOptions |= KEntryMap::EntryExpansion; } break; case 'd': entryOptions |= KEntryMap::EntryDeleted; aKey = aKey.left(start); printableToString(&aKey, file, lineNo); entryMap.setEntry(currentGroup, aKey.toByteArray(), QByteArray(), entryOptions); goto next_line; default: break; } i++; } } else { // found a locale if (!locale.isNull()) { - qWarning() << warningProlog(file, lineNo) + qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (second locale!?)"; goto next_line; } locale = aKey.mid(start + 1, end - start - 1); } aKey.truncate(start); } if (eqpos < 0) { // Do this here after [$d] was checked - qWarning() << warningProlog(file, lineNo) << "Invalid entry (missing '=')"; + qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (missing '=')"; continue; } printableToString(&aKey, file, lineNo); if (!locale.isEmpty()) { if (locale != currentLocale && locale != currentLanguage) { // backward compatibility. C == en_US if (locale.at(0) != 'C' || currentLocale != "en_US") { if (merging) { entryOptions |= KEntryMap::EntryRawKey; } else { goto next_line; // skip this entry if we're not merging } } } } if (!(entryOptions & KEntryMap::EntryRawKey)) { printableToString(&aKey, file, lineNo); } if (options & ParseGlobal) { entryOptions |= KEntryMap::EntryGlobal; } if (bDefault) { entryOptions |= KEntryMap::EntryDefault; } if (!locale.isNull()) { entryOptions |= KEntryMap::EntryLocalized; if (locale.indexOf('_') != -1) { entryOptions |= KEntryMap::EntryLocalizedCountry; } } printableToString(&line, file, lineNo); if (entryOptions & KEntryMap::EntryRawKey) { QByteArray rawKey; rawKey.reserve(aKey.length() + locale.length() + 2); rawKey.append(aKey.toVolatileByteArray()); rawKey.append('[').append(locale.toVolatileByteArray()).append(']'); entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions); } else { entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions); } } next_line: continue; } // now make sure immutable groups are marked immutable for (const QByteArray &group : qAsConst(immutableGroups)) { entryMap.setEntry(group, QByteArray(), QByteArray(), KEntryMap::EntryImmutable); } return fileOptionImmutable ? ParseImmutable : ParseOk; } void KConfigIniBackend::writeEntries(const QByteArray &locale, QIODevice &file, const KEntryMap &map, bool defaultGroup, bool &firstEntry) { QByteArray currentGroup; bool groupIsImmutable = false; const KEntryMapConstIterator end = map.constEnd(); for (KEntryMapConstIterator it = map.constBegin(); it != end; ++it) { const KEntryKey &key = it.key(); // Either process the default group or all others if ((key.mGroup != "") == defaultGroup) { continue; // skip } // the only thing we care about groups is, is it immutable? if (key.mKey.isNull()) { groupIsImmutable = it->bImmutable; continue; // skip } const KEntry ¤tEntry = *it; if (!defaultGroup && currentGroup != key.mGroup) { if (!firstEntry) { file.putChar('\n'); } currentGroup = key.mGroup; for (int start = 0, end;; start = end + 1) { file.putChar('['); end = currentGroup.indexOf('\x1d', start); if (end < 0) { int cgl = currentGroup.length(); if (currentGroup.at(start) == '$' && cgl - start <= 10) { for (int i = start + 1; i < cgl; i++) { char c = currentGroup.at(i); if (c < 'a' || c > 'z') { goto nope; } } file.write("\\x24"); start++; } nope: file.write(stringToPrintable(currentGroup.mid(start), GroupString)); file.putChar(']'); if (groupIsImmutable) { file.write("[$i]", 4); } file.putChar('\n'); break; } else { file.write(stringToPrintable(currentGroup.mid(start, end - start), GroupString)); file.putChar(']'); } } } firstEntry = false; // it is data for a group if (key.bRaw) { // unprocessed key with attached locale from merge file.write(key.mKey); } else { file.write(stringToPrintable(key.mKey, KeyString)); // Key if (key.bLocal && locale != "C") { // 'C' locale == untranslated file.putChar('['); file.write(locale); // locale tag file.putChar(']'); } } if (currentEntry.bDeleted) { if (currentEntry.bImmutable) { file.write("[$di]", 5); // Deleted + immutable } else { file.write("[$d]", 4); // Deleted } } else { if (currentEntry.bImmutable || currentEntry.bExpand) { file.write("[$", 2); if (currentEntry.bImmutable) { file.putChar('i'); } if (currentEntry.bExpand) { file.putChar('e'); } file.putChar(']'); } file.putChar('='); file.write(stringToPrintable(currentEntry.mValue, ValueString)); } file.putChar('\n'); } } void KConfigIniBackend::writeEntries(const QByteArray &locale, QIODevice &file, const KEntryMap &map) { bool firstEntry = true; // write default group writeEntries(locale, file, map, true, firstEntry); // write all other groups writeEntries(locale, file, map, false, firstEntry); } bool KConfigIniBackend::writeConfig(const QByteArray &locale, KEntryMap &entryMap, WriteOptions options) { Q_ASSERT(!filePath().isEmpty()); KEntryMap writeMap; const bool bGlobal = options & WriteGlobal; // First, reparse the file on disk, to merge our changes with the ones done by other apps // Store the result into writeMap. { ParseOptions opts = ParseExpansions; if (bGlobal) { opts |= ParseGlobal; } ParseInfo info = parseConfig(locale, writeMap, opts, true); if (info != ParseOk) { // either there was an error or the file became immutable return false; } } const KEntryMapIterator end = entryMap.end(); for (KEntryMapIterator it = entryMap.begin(); it != end; ++it) { if (!it.key().mKey.isEmpty() && !it->bDirty) { // not dirty, doesn't overwrite entry in writeMap. skips default entries, too. continue; } const KEntryKey &key = it.key(); // only write entries that have the same "globality" as the file if (it->bGlobal == bGlobal) { if (it->bReverted) { writeMap.remove(key); } else if (!it->bDeleted) { writeMap[key] = *it; } else { KEntryKey defaultKey = key; defaultKey.bDefault = true; if (!entryMap.contains(defaultKey)) { writeMap.remove(key); // remove the deleted entry if there is no default //qDebug() << "Detected as deleted=>removed:" << key.mGroup << key.mKey << "global=" << bGlobal; } else { writeMap[key] = *it; // otherwise write an explicitly deleted entry //qDebug() << "Detected as deleted=>[$d]:" << key.mGroup << key.mKey << "global=" << bGlobal; } } it->bDirty = false; } } // now writeMap should contain only entries to be written // so write it out to disk // check if file exists QFile::Permissions fileMode = QFile::ReadUser | QFile::WriteUser; bool createNew = true; QFileInfo fi(filePath()); if (fi.exists()) { #ifdef Q_OS_WIN //TODO: getuid does not exist on windows, use GetSecurityInfo and GetTokenInformation instead createNew = false; #else if (fi.ownerId() == ::getuid()) { // Preserve file mode if file exists and is owned by user. fileMode = fi.permissions(); } else { // File is not owned by user: // Don't create new file but write to existing file instead. createNew = false; } #endif } if (createNew) { QSaveFile file(filePath()); if (!file.open(QIODevice::WriteOnly)) { return false; } file.setTextModeEnabled(true); // to get eol translation writeEntries(locale, file, writeMap); if (!file.size() && (fileMode == (QFile::ReadUser | QFile::WriteUser))) { // File is empty and doesn't have special permissions: delete it. file.cancelWriting(); if (fi.exists()) { // also remove the old file in case it existed. this can happen // when we delete all the entries in an existing config file. // if we don't do this, then deletions and revertToDefault's // will mysteriously fail QFile::remove(filePath()); } } else { // Normal case: Close the file if (file.commit()) { QFile::setPermissions(filePath(), fileMode); return true; } // Couldn't write. Disk full? - qWarning() << "Couldn't write" << filePath() << ". Disk full?"; + qCWarning(KCONFIG_CORE_LOG) << "Couldn't write" << filePath() << ". Disk full?"; return false; } } else { // Open existing file. *DON'T* create it if it suddenly does not exist! #ifdef Q_OS_UNIX int fd = QT_OPEN(QFile::encodeName(filePath()).constData(), O_WRONLY | O_TRUNC); if (fd < 0) { return false; } FILE *fp = ::fdopen(fd, "w"); if (!fp) { QT_CLOSE(fd); return false; } QFile f; if (!f.open(fp, QIODevice::WriteOnly)) { fclose(fp); return false; } writeEntries(locale, f, writeMap); f.close(); fclose(fp); #else QFile f(filePath()); // XXX This is broken - it DOES create the file if it is suddenly gone. if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } f.setTextModeEnabled(true); writeEntries(locale, f, writeMap); #endif } return true; } bool KConfigIniBackend::isWritable() const { const QString filePath = this->filePath(); if (!filePath.isEmpty()) { QFileInfo file(filePath); if (!file.exists()) { // If the file does not exist, check if the deepest // existing dir is writable. QFileInfo dir(file.absolutePath()); while (!dir.exists()) { QString parent = dir.absolutePath(); // Go up. Can't use cdUp() on non-existing dirs. if (parent == dir.filePath()) { // no parent return false; } dir.setFile(parent); } return dir.isDir() && dir.isWritable(); } else { return file.isWritable(); } } return false; } QString KConfigIniBackend::nonWritableErrorMessage() const { return tr("Configuration file \"%1\" not writable.\n").arg(filePath()); } void KConfigIniBackend::createEnclosing() { const QString file = filePath(); if (file.isEmpty()) { return; // nothing to do } // Create the containing dir, maybe it wasn't there QDir dir; dir.mkpath(QFileInfo(file).absolutePath()); } void KConfigIniBackend::setFilePath(const QString &file) { if (file.isEmpty()) { return; } Q_ASSERT(QDir::isAbsolutePath(file)); const QFileInfo info(file); if (info.exists()) { setLocalFilePath(info.canonicalFilePath()); } else { const QString dir = info.dir().canonicalPath(); if (!dir.isEmpty()) setLocalFilePath(dir + QLatin1Char('/') + info.fileName()); else setLocalFilePath(file); } } KConfigBase::AccessMode KConfigIniBackend::accessMode() const { if (filePath().isEmpty()) { return KConfigBase::NoAccess; } if (isWritable()) { return KConfigBase::ReadWrite; } return KConfigBase::ReadOnly; } bool KConfigIniBackend::lock() { Q_ASSERT(!filePath().isEmpty()); if (!lockFile) { lockFile = new QLockFile(filePath() + QLatin1String(".lock")); } lockFile->lock(); return lockFile->isLocked(); } void KConfigIniBackend::unlock() { lockFile->unlock(); delete lockFile; lockFile = nullptr; } bool KConfigIniBackend::isLocked() const { return lockFile && lockFile->isLocked(); } namespace { // serialize an escaped byte at the end of @param data // @param data should have room for 4 bytes char* escapeByte(char* data, unsigned char s) { static const char nibbleLookup[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; *data++ = '\\'; *data++ = 'x'; *data++ = nibbleLookup[s >> 4]; *data++ = nibbleLookup[s & 0x0f]; return data; } // Struct that represents a multi-byte UTF-8 character. // This struct is used to keep track of bytes that seem to be valid // UTF-8. struct Utf8Char { public: unsigned char bytes[4]; unsigned char count; unsigned char charLength; Utf8Char() { clear(); charLength = 0; } void clear() { count = 0; } // Add a byte to the UTF8 character. // When an additional byte leads to an invalid character, return false. bool addByte(unsigned char b) { if (count == 0) { if (b > 0xc1 && (b & 0xe0) == 0xc0) { charLength = 2; } else if ((b & 0xf0) == 0xe0) { charLength = 3; } else if (b < 0xf5 && (b & 0xf8) == 0xf0) { charLength = 4; } else { return false; } bytes[0] = b; count = 1; } else if (count < 4 && (b & 0xc0) == 0x80) { if (count == 1) { if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) { return false; // overlong 3 byte sequence } if (charLength == 4) { if (bytes[0] == 0xf0 && b < 0x90) { return false; // overlong 4 byte sequence } if (bytes[0] == 0xf4 && b > 0x8f) { return false; // Unicode value larger than U+10FFFF } } } bytes[count++] = b; } else { return false; } return true; } // Return true if Utf8Char contains one valid character. bool isComplete() { return count > 0 && count == charLength; } // Add the bytes in this UTF8 character in escaped form to data. char* escapeBytes(char* data) { for (unsigned char i = 0; i < count; ++i) { data = escapeByte(data, bytes[i]); } clear(); return data; } // Add the bytes of the UTF8 character to a buffer. // Only call this if isComplete() returns true. char* writeUtf8(char* data) { for (unsigned char i = 0; i < count; ++i) { *data++ = bytes[i]; } clear(); return data; } // Write the bytes in the UTF8 character literally, or, if the // character is not complete, write the escaped bytes. // This is useful to handle the state that remains after handling // all bytes in a buffer. char* write(char* data) { if (isComplete()) { data = writeUtf8(data); } else { data = escapeBytes(data); } return data; } }; } QByteArray KConfigIniBackend::stringToPrintable(const QByteArray &aString, StringType type) { if (aString.isEmpty()) { return aString; } const int l = aString.length(); QByteArray result; // Guesstimated that it's good to avoid data() initialization for a length of l*4 result.resize(l * 4); // Maximum 4x as long as source string due to \x escape sequences const char *s = aString.constData(); int i = 0; char *data = result.data(); char *start = data; // Protect leading space if (s[0] == ' ' && type != GroupString) { *data++ = '\\'; *data++ = 's'; i++; } Utf8Char utf8; for (; i < l; ++i/*, r++*/) { switch (s[i]) { default: if (utf8.addByte(s[i])) { break; } else { data = utf8.escapeBytes(data); } // The \n, \t, \r cases (all < 32) are handled below; we can ignore them here if (((unsigned char)s[i]) < 32) { goto doEscape; } // GroupString and KeyString should be valid UTF-8, but ValueString // can be a bytearray with non-UTF-8 bytes that should be escaped. if (type == ValueString && ((unsigned char)s[i]) >= 127) { goto doEscape; } *data++ = s[i]; break; case '\n': *data++ = '\\'; *data++ = 'n'; break; case '\t': *data++ = '\\'; *data++ = 't'; break; case '\r': *data++ = '\\'; *data++ = 'r'; break; case '\\': *data++ = '\\'; *data++ = '\\'; break; case '=': if (type != KeyString) { *data++ = s[i]; break; } goto doEscape; case '[': case ']': // Above chars are OK to put in *value* strings as plaintext if (type == ValueString) { *data++ = s[i]; break; } doEscape: data = escapeByte(data, s[i]); break; } if (utf8.isComplete()) { data = utf8.writeUtf8(data); } } data = utf8.write(data); *data = 0; result.resize(data - start); // Protect trailing space if (result.endsWith(' ') && type != GroupString) { result.replace(result.length() - 1, 1, "\\s"); } return result; } char KConfigIniBackend::charFromHex(const char *str, const QFile &file, int line) { unsigned char ret = 0; for (int i = 0; i < 2; i++) { ret <<= 4; quint8 c = quint8(str[i]); if (c >= '0' && c <= '9') { ret |= c - '0'; } else if (c >= 'a' && c <= 'f') { ret |= c - 'a' + 0x0a; } else if (c >= 'A' && c <= 'F') { ret |= c - 'A' + 0x0a; } else { QByteArray e(str, 2); e.prepend("\\x"); - qWarning() << warningProlog(file, line) << "Invalid hex character " << c + qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) << "Invalid hex character " << c << " in \\x-type escape sequence \"" << e.constData() << "\"."; return 'x'; } } return char(ret); } void KConfigIniBackend::printableToString(BufferFragment *aString, const QFile &file, int line) { if (aString->isEmpty() || aString->indexOf('\\') == -1) { return; } aString->trim(); int l = aString->length(); char *r = aString->data(); char *str = r; for (int i = 0; i < l; i++, r++) { if (str[i] != '\\') { *r = str[i]; } else { // Probable escape sequence i++; if (i >= l) { // Line ends after backslash - stop. *r = '\\'; break; } switch (str[i]) { case 's': *r = ' '; break; case 't': *r = '\t'; break; case 'n': *r = '\n'; break; case 'r': *r = '\r'; break; case '\\': *r = '\\'; break; case ';': // not really an escape sequence, but allowed in .desktop files, don't strip '\;' from the string *r = '\\'; r++; *r = ';'; break; case ',': // not really an escape sequence, but allowed in .desktop files, don't strip '\,' from the string *r = '\\'; r++; *r = ','; break; case 'x': if (i + 2 < l) { *r = charFromHex(str + i + 1, file, line); i += 2; } else { *r = 'x'; i = l - 1; } break; default: *r = '\\'; - qWarning() << warningProlog(file, line) + qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) << QStringLiteral("Invalid escape sequence \"\\%1\".").arg(str[i]); } } } aString->truncate(r - aString->constData()); } diff --git a/src/core/kconfigwatcher.cpp b/src/core/kconfigwatcher.cpp index 9a37b6f..2e7defc 100644 --- a/src/core/kconfigwatcher.cpp +++ b/src/core/kconfigwatcher.cpp @@ -1,107 +1,108 @@ /* * Copyright 2018 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfigwatcher.h" #include "config-kconfig.h" +#include "kconfig_core_log_settings.h" #if KCONFIG_USE_DBUS #include #include #include #endif #include #include #include class KConfigWatcherPrivate { public: KSharedConfig::Ptr m_config; }; KConfigWatcher::Ptr KConfigWatcher::create(const KSharedConfig::Ptr &config) { static QThreadStorage>> watcherList; auto c = config.data(); KConfigWatcher::Ptr watcher; if (!watcherList.localData().contains(c)) { watcher = KConfigWatcher::Ptr(new KConfigWatcher(config)); watcherList.localData().insert(c, watcher.toWeakRef()); QObject::connect(watcher.data(), &QObject::destroyed, [c]() { watcherList.localData().remove(c); }); } return watcherList.localData().value(c).toStrongRef(); } KConfigWatcher::KConfigWatcher(const KSharedConfig::Ptr &config): QObject (nullptr), d(new KConfigWatcherPrivate) { Q_ASSERT(config); #if KCONFIG_USE_DBUS qDBusRegisterMetaType(); qDBusRegisterMetaType>(); d->m_config = config; QStringList watchedPaths; watchedPaths <m_config->name(); for (const QString file: d->m_config->additionalConfigSources()) { watchedPaths << QStringLiteral("/") + file; } if (d->m_config->openFlags() & KConfig::IncludeGlobals) { watchedPaths << QStringLiteral("/kdeglobals"); } for(const QString &path: qAsConst(watchedPaths)) { QDBusConnection::sessionBus().connect(QString(), path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged"), this, SLOT(onConfigChangeNotification(QHash))); } #else - qWarning() << "Use of KConfigWatcher without DBus support. You will not receive updates"; + qCWarning(KCONFIG_CORE_LOG) << "Use of KConfigWatcher without DBus support. You will not receive updates"; #endif } void KConfigWatcher::onConfigChangeNotification(const QHash &changes) { //should we ever need it we can determine the file changed with QDbusContext::message().path(), but it doesn't seem too useful d->m_config->reparseConfiguration(); for(auto it = changes.constBegin(); it != changes.constEnd(); it++) { KConfigGroup group = d->m_config->group(QString());//top level group const auto parts = it.key().split(QLatin1Char('\x1d')); //magic char, see KConfig for(const QString &groupName: parts) { group = group.group(groupName); } emit configChanged(group, it.value()); } } diff --git a/src/core/kdesktopfile.cpp b/src/core/kdesktopfile.cpp index d9283ce..367a7d8 100644 --- a/src/core/kdesktopfile.cpp +++ b/src/core/kdesktopfile.cpp @@ -1,387 +1,388 @@ /* This file is part of the KDE libraries Copyright (c) 1999 Pietro Iglio Copyright (c) 1999 Preston Brown This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdesktopfile.h" #ifndef Q_OS_WIN #include #endif #include #include #include #include #include "kauthorized.h" #include "kconfig_p.h" #include "kconfiggroup.h" #include "kconfigini_p.h" +#include "kconfig_core_log_settings.h" class KDesktopFilePrivate : public KConfigPrivate { public: KDesktopFilePrivate(QStandardPaths::StandardLocation resourceType, const QString &fileName); KConfigGroup desktopGroup; }; KDesktopFilePrivate::KDesktopFilePrivate(QStandardPaths::StandardLocation resourceType, const QString &fileName) : KConfigPrivate(KConfig::NoGlobals, resourceType) { mBackend = new KConfigIniBackend(); bDynamicBackend = false; changeFileName(fileName); } KDesktopFile::KDesktopFile(QStandardPaths::StandardLocation resourceType, const QString &fileName) : KConfig(*new KDesktopFilePrivate(resourceType, fileName)) { Q_D(KDesktopFile); reparseConfiguration(); d->desktopGroup = KConfigGroup(this, "Desktop Entry"); } KDesktopFile::KDesktopFile(const QString &fileName) : KConfig(*new KDesktopFilePrivate(QStandardPaths::ApplicationsLocation, fileName)) { Q_D(KDesktopFile); reparseConfiguration(); d->desktopGroup = KConfigGroup(this, "Desktop Entry"); } KDesktopFile::~KDesktopFile() { } KConfigGroup KDesktopFile::desktopGroup() const { Q_D(const KDesktopFile); return d->desktopGroup; } QString KDesktopFile::locateLocal(const QString &path) { QString relativePath; QChar plus(QLatin1Char('/')); // Relative to config? (e.g. for autostart) const QStringList lstGenericConfigLocation = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); for (const QString &dir : lstGenericConfigLocation) { if (path.startsWith(dir + plus)) { relativePath = path.mid(dir.length() + 1); return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + relativePath; } } // Relative to xdg data dir? (much more common) const QStringList lstGenericDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString &dir : lstGenericDataLocation) { if (path.startsWith(dir + plus)) { relativePath = path.mid(dir.length() + 1); } } if (relativePath.isEmpty()) { // What now? The desktop file doesn't come from XDG_DATA_DIRS. Use filename only and hope for the best. relativePath = path.mid(path.lastIndexOf(QLatin1Char('/')) + 1); } return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + relativePath; } bool KDesktopFile::isDesktopFile(const QString &path) { return path.endsWith(QLatin1String(".desktop")); } bool KDesktopFile::isAuthorizedDesktopFile(const QString &path) { if (path.isEmpty()) { return false; // Empty paths are not ok. } if (QDir::isRelativePath(path)) { return true; // Relative paths are ok. } const QString realPath = QFileInfo(path).canonicalFilePath(); if (realPath.isEmpty()) { return false; // File doesn't exist. } #ifndef Q_OS_WIN const Qt::CaseSensitivity sensitivity = Qt::CaseSensitive; #else const Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive; #endif // Check if the .desktop file is installed as part of KDE or XDG. const QStringList appsDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); for (const QString &prefix : appsDirs) { if (QDir(prefix).exists() && realPath.startsWith(QFileInfo(prefix).canonicalFilePath(), sensitivity)) { return true; } } const QString servicesDir = QStringLiteral("kservices5/"); // KGlobal::dirs()->xdgDataRelativePath("services") const QStringList lstGenericDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString &xdgDataPrefix : lstGenericDataLocation) { if (QDir(xdgDataPrefix).exists()) { const QString prefix = QFileInfo(xdgDataPrefix).canonicalFilePath(); if (realPath.startsWith(prefix + QLatin1Char('/') + servicesDir, sensitivity)) { return true; } } } const QString autostartDir = QStringLiteral("autostart/"); const QStringList lstConfigPath = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); for (const QString &xdgDataPrefix : lstConfigPath) { if (QDir(xdgDataPrefix).exists()) { const QString prefix = QFileInfo(xdgDataPrefix).canonicalFilePath(); if (realPath.startsWith(prefix + QLatin1Char('/') + autostartDir, sensitivity)) { return true; } } } // Forbid desktop files outside of standard locations if kiosk is set so if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { - qWarning() << "Access to '" << path << "' denied because of 'run_desktop_files' restriction." << endl; + qCWarning(KCONFIG_CORE_LOG) << "Access to '" << path << "' denied because of 'run_desktop_files' restriction." << endl; return false; } // Not otherwise permitted, so only allow if the file is executable, or if // owned by root (uid == 0) QFileInfo entryInfo(path); if (entryInfo.isExecutable() || entryInfo.ownerId() == 0) { return true; } - qWarning() << "Access to '" << path << "' denied, not owned by root, executable flag not set." << endl; + qCWarning(KCONFIG_CORE_LOG) << "Access to '" << path << "' denied, not owned by root, executable flag not set." << endl; return false; } QString KDesktopFile::readType() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Type", QString()); } QString KDesktopFile::readIcon() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Icon", QString()); } QString KDesktopFile::readName() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Name", QString()); } QString KDesktopFile::readComment() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Comment", QString()); } QString KDesktopFile::readGenericName() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("GenericName", QString()); } QString KDesktopFile::readPath() const { Q_D(const KDesktopFile); // NOT readPathEntry, it is not XDG-compliant: it performs // various expansions, like $HOME. Note that the expansion // behaviour still happens if the "e" flag is set, maintaining // backwards compatibility. return d->desktopGroup.readEntry("Path", QString()); } QString KDesktopFile::readDevice() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Dev", QString()); } QString KDesktopFile::readUrl() const { Q_D(const KDesktopFile); if (hasDeviceType()) { return d->desktopGroup.readEntry("MountPoint", QString()); } else { // NOT readPathEntry (see readPath()) QString url = d->desktopGroup.readEntry("URL", QString()); if (!url.isEmpty() && !QDir::isRelativePath(url)) { // Handle absolute paths as such (i.e. we need to escape them) return QUrl::fromLocalFile(url).toString(); } return url; } } QStringList KDesktopFile::readActions() const { Q_D(const KDesktopFile); return d->desktopGroup.readXdgListEntry("Actions"); } QStringList KDesktopFile::readMimeTypes() const { Q_D(const KDesktopFile); return d->desktopGroup.readXdgListEntry("MimeType"); } KConfigGroup KDesktopFile::actionGroup(const QString &group) { return KConfigGroup(this, QLatin1String("Desktop Action ") + group); } const KConfigGroup KDesktopFile::actionGroup(const QString &group) const { return const_cast(this)->actionGroup(group); } bool KDesktopFile::hasActionGroup(const QString &group) const { return hasGroup(QString(QLatin1String("Desktop Action ") + group).toUtf8().constData()); } bool KDesktopFile::hasLinkType() const { return readType() == QLatin1String("Link"); } bool KDesktopFile::hasApplicationType() const { return readType() == QLatin1String("Application"); } bool KDesktopFile::hasDeviceType() const { return readType() == QLatin1String("FSDevice"); } bool KDesktopFile::tryExec() const { Q_D(const KDesktopFile); // Test for TryExec and "X-KDE-AuthorizeAction" // NOT readPathEntry (see readPath()) QString te = d->desktopGroup.readEntry("TryExec", QString()); if (!te.isEmpty()) { if (QStandardPaths::findExecutable(te).isEmpty()) { return false; } } const QStringList list = d->desktopGroup.readEntry("X-KDE-AuthorizeAction", QStringList()); if (!list.isEmpty()) { for (QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { if (!KAuthorized::authorize((*it).trimmed())) { return false; } } } // See also KService::username() bool su = d->desktopGroup.readEntry("X-KDE-SubstituteUID", false); if (su) { QString user = d->desktopGroup.readEntry("X-KDE-Username", QString()); if (user.isEmpty()) { user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT")); } if (user.isEmpty()) { user = QStringLiteral("root"); } if (!KAuthorized::authorize(QLatin1String("user/") + user)) { return false; } } return true; } /** * @return the filename as passed to the constructor. */ //QString KDesktopFile::fileName() const { return backEnd->fileName(); } /** * @return the resource type as passed to the constructor. */ //QString //KDesktopFile::resource() const { return backEnd->resource(); } #ifndef KDE_NO_DEPRECATED QStringList KDesktopFile::sortOrder() const { Q_D(const KDesktopFile); return d->desktopGroup.readXdgListEntry("SortOrder"); } #endif //void KDesktopFile::virtual_hook( int id, void* data ) //{ KConfig::virtual_hook( id, data ); } QString KDesktopFile::readDocPath() const { Q_D(const KDesktopFile); return d->desktopGroup.readPathEntry("X-DocPath", QString()); } KDesktopFile *KDesktopFile::copyTo(const QString &file) const { KDesktopFile *config = new KDesktopFile(QString()); this->KConfig::copyTo(file, config); // config->setDesktopGroup(); return config; } QStandardPaths::StandardLocation KDesktopFile::resource() const { Q_D(const KDesktopFile); return d->resourceType; } QString KDesktopFile::fileName() const { return name(); } bool KDesktopFile::noDisplay() const { Q_D(const KDesktopFile); if (d->desktopGroup.readEntry("NoDisplay", false)) { return true; } if (d->desktopGroup.hasKey("OnlyShowIn")) { if (!d->desktopGroup.readXdgListEntry("OnlyShowIn").contains(QStringLiteral("KDE"))) { return true; } } if (d->desktopGroup.hasKey("NotShowIn")) { if (d->desktopGroup.readXdgListEntry("NotShowIn").contains(QStringLiteral("KDE"))) { return true; } } return false; }