diff --git a/src/core/kconfigbackend.cpp b/src/core/kconfigbackend.cpp index 9d9fa47..0c18f2c 100644 --- a/src/core/kconfigbackend.cpp +++ b/src/core/kconfigbackend.cpp @@ -1,124 +1,102 @@ /* This file is part of the KDE libraries Copyright (c) 2006 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 "kconfigbackend_p.h" #include #include #include #include #include #include #include "kconfig.h" #include "kconfigini_p.h" #include "kconfigdata.h" typedef QExplicitlySharedDataPointer BackendPtr; class KConfigBackendPrivate { public: - qint64 size; - QDateTime lastModified; QString localFileName; static QString whatSystem(const QString & /*fileName*/) { return QStringLiteral("INI"); } }; void KConfigBackend::registerMappings(const KEntryMap & /*entryMap*/) { } BackendPtr KConfigBackend::create(const QString &file, const QString &sys) { //qDebug() << "creating a backend for file" << file << "with system" << sys; KConfigBackend *backend = nullptr; #if 0 // TODO port to Qt5 plugin loading const QString system = (sys.isEmpty() ? KConfigBackendPrivate::whatSystem(file) : sys); if (system.compare(QLatin1String("INI"), Qt::CaseInsensitive) != 0) { const QString constraint = QString::fromLatin1("[X-KDE-PluginInfo-Name] ~~ '%1'").arg(system); KService::List offers = KServiceTypeTrader::self()->query(QLatin1String("KConfigBackend"), constraint); //qDebug() << "found" << offers.count() << "offers for KConfigBackend plugins with name" << system; foreach (const KService::Ptr &offer, offers) { backend = offer->createInstance(nullptr); if (backend) { //qDebug() << "successfully created a backend for" << system; backend->setFilePath(file); return BackendPtr(backend); } } // foreach offers } #else Q_UNUSED(sys); #endif //qDebug() << "default creation of the Ini backend"; backend = new KConfigIniBackend; backend->setFilePath(file); return BackendPtr(backend); } KConfigBackend::KConfigBackend() : d(new KConfigBackendPrivate) { } KConfigBackend::~KConfigBackend() { delete d; } -QDateTime KConfigBackend::lastModified() const -{ - return d->lastModified; -} - -void KConfigBackend::setLastModified(const QDateTime &dt) -{ - d->lastModified = dt; -} - -qint64 KConfigBackend::size() const -{ - return d->size; -} - -void KConfigBackend::setSize(qint64 sz) -{ - d->size = sz; -} - QString KConfigBackend::filePath() const { return d->localFileName; } void KConfigBackend::setLocalFilePath(const QString &file) { d->localFileName = file; } diff --git a/src/core/kconfigbackend_p.h b/src/core/kconfigbackend_p.h index 0a63a68..d2e3de4 100644 --- a/src/core/kconfigbackend_p.h +++ b/src/core/kconfigbackend_p.h @@ -1,214 +1,205 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Portions 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. */ #ifndef KCONFIGBACKEND_H #define KCONFIGBACKEND_H #include #include #include #include #include class KConfigBackendPrivate; class KEntryMap; class QFile; class QByteArray; -class QDateTime; /** * \class KConfigBackend kconfigbackend_p.h * * Provides the implementation for accessing configuration sources. * * KConfig only provides an INI backend, but this class can be used * to create plugins that allow access to other file formats and * configuration systems. * * \internal */ class KConfigBackend : public QObject, public QSharedData { Q_OBJECT public: /** * Creates a new KConfig backend. * * If no @p system is given, or the given @p system is unknown, this method tries * to determine the correct backend to use. * * @param fileName the absolute file name of the configuration file * @param system the configuration system to use * @return a KConfigBackend object to be used with KConfig */ static QExplicitlySharedDataPointer create(const QString &fileName = QString(), const QString &system = QString()); /** * Registers mappings from directories/files to configuration systems * * Allows you to tell KConfigBackend that create() should use a particular * backend for a particular file or directory. * * @warning currently does nothing * * @param entryMap the KEntryMap to build the mappings from */ static void registerMappings(const KEntryMap &entryMap); /** Destroys the backend */ virtual ~KConfigBackend(); /** Allows the behaviour of parseConfig() to be tuned */ enum ParseOption { ParseGlobal = 1, /// entries should be marked as @em global ParseDefaults = 2, /// entries should be marked as @em default ParseExpansions = 4 /// entries are allowed to be marked as @em expandable }; Q_FLAG(ParseOption) /// @typedef typedef QFlags ParseOptions Q_DECLARE_FLAGS(ParseOptions, ParseOption) /** Allows the behaviour of writeConfig() to be tuned */ enum WriteOption { WriteGlobal = 1 /// only write entries marked as "global" }; Q_FLAG(WriteOption) /// @typedef typedef QFlags WriteOptions Q_DECLARE_FLAGS(WriteOptions, WriteOption) /** Return value from parseConfig() */ enum ParseInfo { ParseOk, /// the configuration was opened read/write ParseImmutable, /// the configuration is @em immutable ParseOpenError /// the configuration could not be opened }; /** * Read persistent storage * * @param locale the locale to read entries for (if the backend supports localized entries) * @param pWriteBackMap the KEntryMap where the entries are placed * @param options See ParseOptions * @return See ParseInfo */ virtual ParseInfo parseConfig(const QByteArray &locale, KEntryMap &pWriteBackMap, ParseOptions options = ParseOptions()) = 0; /** * Write the @em dirty entries to permanent storage * * @param locale the locale to write entries for (if the backend supports localized entries) * @param entryMap the KEntryMap containing the config object's entries. * @param options See WriteOptions * * @return @c true if the write was successful, @c false if writing the configuration failed */ virtual bool writeConfig(const QByteArray &locale, KEntryMap &entryMap, WriteOptions options) = 0; /** * If isWritable() returns false, writeConfig() will always fail. * * @return @c true if the configuration is writable, @c false if it is immutable */ virtual bool isWritable() const = 0; /** * When isWritable() returns @c false, return an error message to * explain to the user why saving configuration will not work. * * The return value when isWritable() returns @c true is undefined. * * @returns a translated user-visible explanation for the configuration * object not being writable */ virtual QString nonWritableErrorMessage() const = 0; /** * @return the read/write status of the configuration object * * @see KConfigBase::AccessMode */ virtual KConfigBase::AccessMode accessMode() const = 0; /** * Create the enclosing object of the configuration object * * For example, if the configuration object is a file, this should create * the parent directory. */ virtual void createEnclosing() = 0; /** * Set the file path. * * @note @p path @b MUST be @em absolute. * * @param path the absolute file path */ virtual void setFilePath(const QString &path) = 0; /** * Lock the file */ virtual bool lock() = 0; /** * Release the lock on the file */ virtual void unlock() = 0; /** * @return @c true if the file is locked, @c false if it is not locked */ virtual bool isLocked() const = 0; - /** - * @return the date and time when the object was last modified - */ - QDateTime lastModified() const; /** @return the absolute path to the object */ QString filePath() const; - /** @return the size of the object */ - qint64 size() const; protected: KConfigBackend(); - void setLastModified(const QDateTime &dt); - void setSize(qint64 sz); void setLocalFilePath(const QString &file); private: KConfigBackendPrivate *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KConfigBackend::ParseOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(KConfigBackend::WriteOptions) #if 0 // TODO re-enable if the plugin loading code is re-enabled /** * Register a KConfig backend when it is contained in a loadable module */ #define K_EXPORT_KCONFIGBACKEND(libname, classname) \ K_PLUGIN_FACTORY(factory, registerPlugin();) #endif #endif // KCONFIGBACKEND_H diff --git a/src/core/kconfigini.cpp b/src/core/kconfigini.cpp index 49ec150..a5ecc00 100644 --- a/src/core/kconfigini.cpp +++ b/src/core/kconfigini.cpp @@ -1,818 +1,812 @@ /* 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 #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."; // 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); } if (aKey.isEmpty()) { qWarning() << 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) << "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) << "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 '=')"; 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 Q_FOREACH (const QByteArray &group, 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?"; 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()); - setLastModified(info.lastModified()); - setSize(info.size()); } else { setLocalFilePath(file); - setSize(0); - QDateTime dummy; - dummy.setTime_t(0); - setLastModified(dummy); } } 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(); } QByteArray KConfigIniBackend::stringToPrintable(const QByteArray &aString, StringType type) { static const char nibbleLookup[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 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++; } for (; i < l; ++i/*, r++*/) { switch (s[i]) { default: // The \n, \t, \r cases (all < 32) are handled below; we can ignore them here if (((unsigned char)s[i]) < 32) { 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++ = '\\'; *data++ = 'x'; *data++ = nibbleLookup[((unsigned char)s[i]) >> 4]; *data++ = nibbleLookup[((unsigned char)s[i]) & 0x0f]; break; } } *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 << " 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 '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) << QStringLiteral("Invalid escape sequence \"\\%1\".").arg(str[i]); } } } aString->truncate(r - aString->constData()); }