File indexing completed on 2024-05-12 15:34:11

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
0004     SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
0005     SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kconfigini_p.h"
0011 
0012 #include "bufferfragment_p.h"
0013 #include "kconfig.h"
0014 #include "kconfig_core_log_settings.h"
0015 #include "kconfigbackend_p.h"
0016 #include "kconfigdata_p.h"
0017 
0018 #include <QDateTime>
0019 #include <QDebug>
0020 #include <QDir>
0021 #include <QFile>
0022 #include <QFileInfo>
0023 #include <QLockFile>
0024 #include <QSaveFile>
0025 #include <qplatformdefs.h>
0026 
0027 #ifndef Q_OS_WIN
0028 #include <unistd.h> // getuid, close
0029 #endif
0030 #include <fcntl.h> // open
0031 #include <sys/types.h> // uid_t
0032 
0033 KCONFIGCORE_EXPORT bool kde_kiosk_exception = false; // flag to disable kiosk restrictions
0034 
0035 static QByteArray lookup(const KConfigIniBackend::BufferFragment fragment, QHash<KConfigIniBackend::BufferFragment, QByteArray> *cache)
0036 {
0037     auto it = cache->constFind(fragment);
0038     if (it != cache->constEnd()) {
0039         return it.value();
0040     }
0041 
0042     return cache->insert(fragment, fragment.toByteArray()).value();
0043 }
0044 
0045 QString KConfigIniBackend::warningProlog(const QFile &file, int line)
0046 {
0047     // %2 then %1 i.e. int before QString, so that the QString is last
0048     // This avoids a wrong substitution if the fileName itself contains %1
0049     return QStringLiteral("KConfigIni: In file %2, line %1: ").arg(line).arg(file.fileName());
0050 }
0051 
0052 KConfigIniBackend::KConfigIniBackend()
0053     : KConfigBackend()
0054     , lockFile(nullptr)
0055 {
0056 }
0057 
0058 KConfigIniBackend::~KConfigIniBackend()
0059 {
0060 }
0061 
0062 KConfigBackend::ParseInfo KConfigIniBackend::parseConfig(const QByteArray &currentLocale, KEntryMap &entryMap, ParseOptions options)
0063 {
0064     return parseConfig(currentLocale, entryMap, options, false);
0065 }
0066 
0067 // merging==true is the merging that happens at the beginning of writeConfig:
0068 // merge changes in the on-disk file with the changes in the KConfig object.
0069 KConfigBackend::ParseInfo KConfigIniBackend::parseConfig(const QByteArray &currentLocale, KEntryMap &entryMap, ParseOptions options, bool merging)
0070 {
0071     if (filePath().isEmpty()) {
0072         return ParseOk;
0073     }
0074 
0075     QFile file(filePath());
0076     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0077         return file.exists() ? ParseOpenError : ParseOk;
0078     }
0079 
0080     QList<QByteArray> immutableGroups;
0081 
0082     bool fileOptionImmutable = false;
0083     bool groupOptionImmutable = false;
0084     bool groupSkip = false;
0085 
0086     int lineNo = 0;
0087     // on systems using \r\n as end of line, \r will be taken care of by
0088     // trim() below
0089     QByteArray buffer = file.readAll();
0090     BufferFragment contents(buffer.data(), buffer.size());
0091     unsigned int len = contents.length();
0092     unsigned int startOfLine = 0;
0093 
0094     const int langIdx = currentLocale.indexOf('_');
0095     const QByteArray currentLanguage = langIdx >= 0 ? currentLocale.left(langIdx) : currentLocale;
0096 
0097     QByteArray currentGroup("<default>");
0098     bool bDefault = options & ParseDefaults;
0099     bool allowExecutableValues = options & ParseExpansions;
0100 
0101     // Reduce memory overhead by making use of implicit sharing
0102     // This assumes that config files contain only a small amount of
0103     // different fragments which are repeated often.
0104     // This is often the case, especially sub groups will all have
0105     // the same list of keys and similar values as well.
0106     QHash<BufferFragment, QByteArray> cache;
0107     cache.reserve(4096);
0108 
0109     while (startOfLine < len) {
0110         BufferFragment line = contents.split('\n', &startOfLine);
0111         line.trim();
0112         ++lineNo;
0113 
0114         // skip empty lines and lines beginning with '#'
0115         if (line.isEmpty() || line.at(0) == '#') {
0116             continue;
0117         }
0118 
0119         if (line.at(0) == '[') { // found a group
0120             groupOptionImmutable = fileOptionImmutable;
0121 
0122             QByteArray newGroup;
0123             int start = 1;
0124             int end = 0;
0125             do {
0126                 end = start;
0127                 for (;;) {
0128                     if (end == line.length()) {
0129                         qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid group header.";
0130                         // XXX maybe reset the current group here?
0131                         goto next_line;
0132                     }
0133                     if (line.at(end) == ']') {
0134                         break;
0135                     }
0136                     ++end;
0137                 }
0138                 /* clang-format off */
0139                 if (end + 1 == line.length()
0140                     && start + 2 == end
0141                     && line.at(start) == '$'
0142                     && line.at(start + 1) == 'i') { /* clang-format on */
0143                     if (newGroup.isEmpty()) {
0144                         fileOptionImmutable = !kde_kiosk_exception;
0145                     } else {
0146                         groupOptionImmutable = !kde_kiosk_exception;
0147                     }
0148                 } else {
0149                     if (!newGroup.isEmpty()) {
0150                         newGroup += '\x1d';
0151                     }
0152                     BufferFragment namePart = line.mid(start, end - start);
0153                     printableToString(&namePart, file, lineNo);
0154                     newGroup += namePart.toByteArray();
0155                 }
0156             } while ((start = end + 2) <= line.length() && line.at(end + 1) == '[');
0157             currentGroup = newGroup;
0158 
0159             groupSkip = entryMap.getEntryOption(currentGroup, {}, {}, KEntryMap::EntryImmutable);
0160 
0161             if (groupSkip && !bDefault) {
0162                 continue;
0163             }
0164 
0165             if (groupOptionImmutable)
0166             // Do not make the groups immutable until the entries from
0167             // this file have been added.
0168             {
0169                 immutableGroups.append(currentGroup);
0170             }
0171         } else {
0172             if (groupSkip && !bDefault) {
0173                 continue; // skip entry
0174             }
0175 
0176             BufferFragment aKey;
0177             int eqpos = line.indexOf('=');
0178             if (eqpos < 0) {
0179                 aKey = line;
0180                 line.clear();
0181             } else {
0182                 BufferFragment temp = line.left(eqpos);
0183                 temp.trim();
0184                 aKey = temp;
0185                 line.truncateLeft(eqpos + 1);
0186                 line.trim();
0187             }
0188             if (aKey.isEmpty()) {
0189                 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (empty key)";
0190                 continue;
0191             }
0192 
0193             KEntryMap::EntryOptions entryOptions = {};
0194             if (groupOptionImmutable) {
0195                 entryOptions |= KEntryMap::EntryImmutable;
0196             }
0197 
0198             BufferFragment locale;
0199             int start;
0200             while ((start = aKey.lastIndexOf('[')) >= 0) {
0201                 int end = aKey.indexOf(']', start);
0202                 if (end < 0) {
0203                     qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (missing ']')";
0204                     goto next_line;
0205                 } else if (end > start + 1 && aKey.at(start + 1) == '$') { // found option(s)
0206                     int i = start + 2;
0207                     while (i < end) {
0208                         switch (aKey.at(i)) {
0209                         case 'i':
0210                             if (!kde_kiosk_exception) {
0211                                 entryOptions |= KEntryMap::EntryImmutable;
0212                             }
0213                             break;
0214                         case 'e':
0215                             if (allowExecutableValues) {
0216                                 entryOptions |= KEntryMap::EntryExpansion;
0217                             }
0218                             break;
0219                         case 'd':
0220                             entryOptions |= KEntryMap::EntryDeleted;
0221                             aKey.truncate(start);
0222                             printableToString(&aKey, file, lineNo);
0223                             entryMap.setEntry(currentGroup, aKey.toByteArray(), QByteArray(), entryOptions);
0224                             goto next_line;
0225                         default:
0226                             break;
0227                         }
0228                         ++i;
0229                     }
0230                 } else { // found a locale
0231                     if (!locale.isNull()) {
0232                         qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (second locale!?)";
0233                         goto next_line;
0234                     }
0235 
0236                     locale = aKey.mid(start + 1, end - start - 1);
0237                 }
0238                 aKey.truncate(start);
0239             }
0240             if (eqpos < 0) { // Do this here after [$d] was checked
0241                 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (missing '=')";
0242                 continue;
0243             }
0244             printableToString(&aKey, file, lineNo);
0245             if (!locale.isEmpty()) {
0246                 if (locale != currentLocale && locale != currentLanguage) {
0247                     // backward compatibility. C == en_US
0248                     if (locale.at(0) != 'C' || currentLocale != "en_US") {
0249                         if (merging) {
0250                             entryOptions |= KEntryMap::EntryRawKey;
0251                         } else {
0252                             goto next_line; // skip this entry if we're not merging
0253                         }
0254                     }
0255                 }
0256             }
0257 
0258             if (!(entryOptions & KEntryMap::EntryRawKey)) {
0259                 printableToString(&aKey, file, lineNo);
0260             }
0261 
0262             if (options & ParseGlobal) {
0263                 entryOptions |= KEntryMap::EntryGlobal;
0264             }
0265             if (bDefault) {
0266                 entryOptions |= KEntryMap::EntryDefault;
0267             }
0268             if (!locale.isNull()) {
0269                 entryOptions |= KEntryMap::EntryLocalized;
0270                 if (locale.indexOf('_') != -1) {
0271                     entryOptions |= KEntryMap::EntryLocalizedCountry;
0272                 }
0273             }
0274             printableToString(&line, file, lineNo);
0275             if (entryOptions & KEntryMap::EntryRawKey) {
0276                 QByteArray rawKey;
0277                 rawKey.reserve(aKey.length() + locale.length() + 2);
0278                 rawKey.append(aKey.toVolatileByteArray());
0279                 rawKey.append('[').append(locale.toVolatileByteArray()).append(']');
0280                 entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions);
0281             } else {
0282                 entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions);
0283             }
0284         }
0285     next_line:
0286         continue;
0287     }
0288 
0289     // now make sure immutable groups are marked immutable
0290     for (const QByteArray &group : std::as_const(immutableGroups)) {
0291         entryMap.setEntry(group, QByteArray(), QByteArray(), KEntryMap::EntryImmutable);
0292     }
0293 
0294     return fileOptionImmutable ? ParseImmutable : ParseOk;
0295 }
0296 
0297 void KConfigIniBackend::writeEntries(const QByteArray &locale, QIODevice &file, const KEntryMap &map, bool defaultGroup, bool &firstEntry)
0298 {
0299     QByteArray currentGroup;
0300     bool groupIsImmutable = false;
0301     const auto end = map.cend();
0302     for (auto it = map.cbegin(); it != end; ++it) {
0303         const KEntryKey &key = it.key();
0304 
0305         // Either process the default group or all others
0306         if ((key.mGroup != "<default>") == defaultGroup) {
0307             continue; // skip
0308         }
0309 
0310         // the only thing we care about groups is, is it immutable?
0311         if (key.mKey.isNull()) {
0312             groupIsImmutable = it->bImmutable;
0313             continue; // skip
0314         }
0315 
0316         const KEntry &currentEntry = *it;
0317         if (!defaultGroup && currentGroup != key.mGroup) {
0318             if (!firstEntry) {
0319                 file.putChar('\n');
0320             }
0321             currentGroup = key.mGroup;
0322             for (int start = 0, end;; start = end + 1) {
0323                 file.putChar('[');
0324                 end = currentGroup.indexOf('\x1d', start);
0325                 if (end < 0) {
0326                     int cgl = currentGroup.length();
0327                     if (currentGroup.at(start) == '$' && cgl - start <= 10) {
0328                         for (int i = start + 1; i < cgl; i++) {
0329                             char c = currentGroup.at(i);
0330                             if (c < 'a' || c > 'z') {
0331                                 goto nope;
0332                             }
0333                         }
0334                         file.write("\\x24");
0335                         ++start;
0336                     }
0337                 nope:
0338                     file.write(stringToPrintable(currentGroup.mid(start), GroupString));
0339                     file.putChar(']');
0340                     if (groupIsImmutable) {
0341                         file.write("[$i]", 4);
0342                     }
0343                     file.putChar('\n');
0344                     break;
0345                 } else {
0346                     file.write(stringToPrintable(currentGroup.mid(start, end - start), GroupString));
0347                     file.putChar(']');
0348                 }
0349             }
0350         }
0351 
0352         firstEntry = false;
0353         // it is data for a group
0354 
0355         if (key.bRaw) { // unprocessed key with attached locale from merge
0356             file.write(key.mKey);
0357         } else {
0358             file.write(stringToPrintable(key.mKey, KeyString)); // Key
0359             if (key.bLocal && locale != "C") { // 'C' locale == untranslated
0360                 file.putChar('[');
0361                 file.write(locale); // locale tag
0362                 file.putChar(']');
0363             }
0364         }
0365         if (currentEntry.bDeleted) {
0366             if (currentEntry.bImmutable) {
0367                 file.write("[$di]", 5); // Deleted + immutable
0368             } else {
0369                 file.write("[$d]", 4); // Deleted
0370             }
0371         } else {
0372             if (currentEntry.bImmutable || currentEntry.bExpand) {
0373                 file.write("[$", 2);
0374                 if (currentEntry.bImmutable) {
0375                     file.putChar('i');
0376                 }
0377                 if (currentEntry.bExpand) {
0378                     file.putChar('e');
0379                 }
0380                 file.putChar(']');
0381             }
0382             file.putChar('=');
0383             file.write(stringToPrintable(currentEntry.mValue, ValueString));
0384         }
0385         file.putChar('\n');
0386     }
0387 }
0388 
0389 void KConfigIniBackend::writeEntries(const QByteArray &locale, QIODevice &file, const KEntryMap &map)
0390 {
0391     bool firstEntry = true;
0392 
0393     // write default group
0394     writeEntries(locale, file, map, true, firstEntry);
0395 
0396     // write all other groups
0397     writeEntries(locale, file, map, false, firstEntry);
0398 }
0399 
0400 bool KConfigIniBackend::writeConfig(const QByteArray &locale, KEntryMap &entryMap, WriteOptions options)
0401 {
0402     Q_ASSERT(!filePath().isEmpty());
0403 
0404     KEntryMap writeMap;
0405     const bool bGlobal = options & WriteGlobal;
0406 
0407     // First, reparse the file on disk, to merge our changes with the ones done by other apps
0408     // Store the result into writeMap.
0409     {
0410         ParseOptions opts = ParseExpansions;
0411         if (bGlobal) {
0412             opts |= ParseGlobal;
0413         }
0414         ParseInfo info = parseConfig(locale, writeMap, opts, true);
0415         if (info != ParseOk) { // either there was an error or the file became immutable
0416             return false;
0417         }
0418     }
0419 
0420     const KEntryMapIterator end = entryMap.end();
0421     for (KEntryMapIterator it = entryMap.begin(); it != end; ++it) {
0422         if (!it.key().mKey.isEmpty() && !it->bDirty) { // not dirty, doesn't overwrite entry in writeMap. skips default entries, too.
0423             continue;
0424         }
0425 
0426         const KEntryKey &key = it.key();
0427 
0428         // only write entries that have the same "globality" as the file
0429         if (it->bGlobal == bGlobal) {
0430             if (it->bReverted && it->bOverridesGlobal) {
0431                 it->bDeleted = true;
0432                 writeMap[key] = *it;
0433             } else if (it->bReverted) {
0434                 writeMap.remove(key);
0435             } else if (!it->bDeleted) {
0436                 writeMap[key] = *it;
0437             } else {
0438                 KEntryKey defaultKey = key;
0439                 defaultKey.bDefault = true;
0440                 if (!entryMap.contains(defaultKey) && !it->bOverridesGlobal) {
0441                     writeMap.remove(key); // remove the deleted entry if there is no default
0442                     // qDebug() << "Detected as deleted=>removed:" << key.mGroup << key.mKey << "global=" << bGlobal;
0443                 } else {
0444                     writeMap[key] = *it; // otherwise write an explicitly deleted entry
0445                     // qDebug() << "Detected as deleted=>[$d]:" << key.mGroup << key.mKey << "global=" << bGlobal;
0446                 }
0447             }
0448             it->bDirty = false;
0449         }
0450     }
0451 
0452     // now writeMap should contain only entries to be written
0453     // so write it out to disk
0454 
0455     // check if file exists
0456     QFile::Permissions fileMode = QFile::ReadUser | QFile::WriteUser;
0457     bool createNew = true;
0458 
0459     QFileInfo fi(filePath());
0460     if (fi.exists()) {
0461 #ifdef Q_OS_WIN
0462         // TODO: getuid does not exist on windows, use GetSecurityInfo and GetTokenInformation instead
0463         createNew = false;
0464 #else
0465         if (fi.ownerId() == ::getuid()) {
0466             // Preserve file mode if file exists and is owned by user.
0467             fileMode = fi.permissions();
0468         } else {
0469             // File is not owned by user:
0470             // Don't create new file but write to existing file instead.
0471             createNew = false;
0472         }
0473 #endif
0474     }
0475 
0476     if (createNew) {
0477         QSaveFile file(filePath());
0478         if (!file.open(QIODevice::WriteOnly)) {
0479 #ifdef Q_OS_ANDROID
0480             // HACK: when we are dealing with content:// URIs, QSaveFile has to rely on DirectWrite.
0481             // Otherwise this method returns a false and we're done.
0482             file.setDirectWriteFallback(true);
0483             if (!file.open(QIODevice::WriteOnly)) {
0484                 qWarning(KCONFIG_CORE_LOG) << "Couldn't create a new file:" << filePath() << ". Error:" << file.errorString();
0485                 return false;
0486             }
0487 #else
0488             qWarning(KCONFIG_CORE_LOG) << "Couldn't create a new file:" << filePath() << ". Error:" << file.errorString();
0489             return false;
0490 #endif
0491         }
0492 
0493         file.setTextModeEnabled(true); // to get eol translation
0494         writeEntries(locale, file, writeMap);
0495 
0496         if (!file.size() && (fileMode == (QFile::ReadUser | QFile::WriteUser))) {
0497             // File is empty and doesn't have special permissions: delete it.
0498             file.cancelWriting();
0499 
0500             if (fi.exists()) {
0501                 // also remove the old file in case it existed. this can happen
0502                 // when we delete all the entries in an existing config file.
0503                 // if we don't do this, then deletions and revertToDefault's
0504                 // will mysteriously fail
0505                 QFile::remove(filePath());
0506             }
0507         } else {
0508             // Normal case: Close the file
0509             if (file.commit()) {
0510                 QFile::setPermissions(filePath(), fileMode);
0511                 return true;
0512             }
0513             // Couldn't write. Disk full?
0514             qCWarning(KCONFIG_CORE_LOG) << "Couldn't write" << filePath() << ". Disk full?";
0515             return false;
0516         }
0517     } else {
0518         // Open existing file. *DON'T* create it if it suddenly does not exist!
0519 #if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
0520         int fd = QT_OPEN(QFile::encodeName(filePath()).constData(), O_WRONLY | O_TRUNC);
0521         if (fd < 0) {
0522             return false;
0523         }
0524         QFile f;
0525         if (!f.open(fd, QIODevice::WriteOnly)) {
0526             QT_CLOSE(fd);
0527             return false;
0528         }
0529         writeEntries(locale, f, writeMap);
0530         f.close();
0531         QT_CLOSE(fd);
0532 #else
0533         QFile f(filePath());
0534         // XXX This is broken - it DOES create the file if it is suddenly gone.
0535         if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
0536             return false;
0537         }
0538         f.setTextModeEnabled(true);
0539         writeEntries(locale, f, writeMap);
0540 #endif
0541     }
0542     return true;
0543 }
0544 
0545 bool KConfigIniBackend::isWritable() const
0546 {
0547     const QString filePath = this->filePath();
0548     if (filePath.isEmpty()) {
0549         return false;
0550     }
0551 
0552     QFileInfo file(filePath);
0553     if (file.exists()) {
0554         return file.isWritable();
0555     }
0556 
0557     // If the file does not exist, check if the deepest existing dir is writable
0558     QFileInfo dir(file.absolutePath());
0559     while (!dir.exists()) {
0560         QString parent = dir.absolutePath(); // Go up. Can't use cdUp() on non-existing dirs.
0561         if (parent == dir.filePath()) {
0562             // no parent
0563             return false;
0564         }
0565         dir.setFile(parent);
0566     }
0567     return dir.isDir() && dir.isWritable();
0568 }
0569 
0570 QString KConfigIniBackend::nonWritableErrorMessage() const
0571 {
0572     return tr("Configuration file \"%1\" not writable.\n").arg(filePath());
0573 }
0574 
0575 void KConfigIniBackend::createEnclosing()
0576 {
0577     const QString file = filePath();
0578     if (file.isEmpty()) {
0579         return; // nothing to do
0580     }
0581 
0582     // Create the containing dir, maybe it wasn't there
0583     QDir().mkpath(QFileInfo(file).absolutePath());
0584 }
0585 
0586 void KConfigIniBackend::setFilePath(const QString &path)
0587 {
0588     if (path.isEmpty()) {
0589         return;
0590     }
0591 
0592     Q_ASSERT(QDir::isAbsolutePath(path));
0593 
0594     const QFileInfo info(path);
0595     if (info.exists()) {
0596         setLocalFilePath(info.canonicalFilePath());
0597         return;
0598     }
0599 
0600     if (QString filePath = info.dir().canonicalPath(); !filePath.isEmpty()) {
0601         filePath += QLatin1Char('/') + info.fileName();
0602         setLocalFilePath(filePath);
0603     } else {
0604         setLocalFilePath(path);
0605     }
0606 }
0607 
0608 KConfigBase::AccessMode KConfigIniBackend::accessMode() const
0609 {
0610     if (filePath().isEmpty()) {
0611         return KConfigBase::NoAccess;
0612     }
0613 
0614     if (isWritable()) {
0615         return KConfigBase::ReadWrite;
0616     }
0617 
0618     return KConfigBase::ReadOnly;
0619 }
0620 
0621 bool KConfigIniBackend::lock()
0622 {
0623     Q_ASSERT(!filePath().isEmpty());
0624 
0625     m_mutex.lock();
0626 #ifdef Q_OS_ANDROID
0627     if (!lockFile) {
0628         // handle content Uris properly
0629         if (filePath().startsWith(QLatin1String("content://"))) {
0630             // we can't create file at an arbitrary location, so use internal storage to create one
0631 
0632             // NOTE: filename can be the same, but because this lock is short lived we may never have a collision
0633             lockFile = new QLockFile(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/")
0634                                      + QFileInfo(filePath()).fileName() + QLatin1String(".lock"));
0635         } else {
0636             lockFile = new QLockFile(filePath() + QLatin1String(".lock"));
0637         }
0638     }
0639 #else
0640     if (!lockFile) {
0641         lockFile = new QLockFile(filePath() + QLatin1String(".lock"));
0642     }
0643 #endif
0644 
0645     if (!lockFile->lock()) {
0646         m_mutex.unlock();
0647     }
0648 
0649     return lockFile->isLocked();
0650 }
0651 
0652 void KConfigIniBackend::unlock()
0653 {
0654     lockFile->unlock();
0655     delete lockFile;
0656     lockFile = nullptr;
0657     m_mutex.unlock();
0658 }
0659 
0660 bool KConfigIniBackend::isLocked() const
0661 {
0662     return lockFile && lockFile->isLocked();
0663 }
0664 
0665 namespace
0666 {
0667 // serialize an escaped byte at the end of @param data
0668 // @param data should have room for 4 bytes
0669 char *escapeByte(char *data, unsigned char s)
0670 {
0671     static const char nibbleLookup[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
0672     *data++ = '\\';
0673     *data++ = 'x';
0674     *data++ = nibbleLookup[s >> 4];
0675     *data++ = nibbleLookup[s & 0x0f];
0676     return data;
0677 }
0678 
0679 // Struct that represents a multi-byte UTF-8 character.
0680 // This struct is used to keep track of bytes that seem to be valid
0681 // UTF-8.
0682 struct Utf8Char {
0683 public:
0684     unsigned char bytes[4];
0685     unsigned char count;
0686     unsigned char charLength;
0687 
0688     Utf8Char()
0689     {
0690         clear();
0691         charLength = 0;
0692     }
0693     void clear()
0694     {
0695         count = 0;
0696     }
0697     // Add a byte to the UTF8 character.
0698     // When an additional byte leads to an invalid character, return false.
0699     bool addByte(unsigned char b)
0700     {
0701         if (count == 0) {
0702             if (b > 0xc1 && (b & 0xe0) == 0xc0) {
0703                 charLength = 2;
0704             } else if ((b & 0xf0) == 0xe0) {
0705                 charLength = 3;
0706             } else if (b < 0xf5 && (b & 0xf8) == 0xf0) {
0707                 charLength = 4;
0708             } else {
0709                 return false;
0710             }
0711             bytes[0] = b;
0712             count = 1;
0713         } else if (count < 4 && (b & 0xc0) == 0x80) {
0714             if (count == 1) {
0715                 if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) {
0716                     return false; // overlong 3 byte sequence
0717                 }
0718                 if (charLength == 4) {
0719                     if (bytes[0] == 0xf0 && b < 0x90) {
0720                         return false; // overlong 4 byte sequence
0721                     }
0722                     if (bytes[0] == 0xf4 && b > 0x8f) {
0723                         return false; // Unicode value larger than U+10FFFF
0724                     }
0725                 }
0726             }
0727             bytes[count++] = b;
0728         } else {
0729             return false;
0730         }
0731         return true;
0732     }
0733     // Return true if Utf8Char contains one valid character.
0734     bool isComplete()
0735     {
0736         return count > 0 && count == charLength;
0737     }
0738     // Add the bytes in this UTF8 character in escaped form to data.
0739     char *escapeBytes(char *data)
0740     {
0741         for (unsigned char i = 0; i < count; ++i) {
0742             data = escapeByte(data, bytes[i]);
0743         }
0744         clear();
0745         return data;
0746     }
0747     // Add the bytes of the UTF8 character to a buffer.
0748     // Only call this if isComplete() returns true.
0749     char *writeUtf8(char *data)
0750     {
0751         for (unsigned char i = 0; i < count; ++i) {
0752             *data++ = bytes[i];
0753         }
0754         clear();
0755         return data;
0756     }
0757     // Write the bytes in the UTF8 character literally, or, if the
0758     // character is not complete, write the escaped bytes.
0759     // This is useful to handle the state that remains after handling
0760     // all bytes in a buffer.
0761     char *write(char *data)
0762     {
0763         if (isComplete()) {
0764             data = writeUtf8(data);
0765         } else {
0766             data = escapeBytes(data);
0767         }
0768         return data;
0769     }
0770 };
0771 }
0772 
0773 QByteArray KConfigIniBackend::stringToPrintable(const QByteArray &aString, StringType type)
0774 {
0775     const int len = aString.size();
0776     if (len == 0) {
0777         return aString;
0778     }
0779 
0780     QByteArray result; // Guesstimated that it's good to avoid data() initialization for a length of len*4
0781     result.resize(len * 4); // Maximum 4x as long as source string due to \x<ab> escape sequences
0782     const char *s = aString.constData();
0783     int i = 0;
0784     char *data = result.data();
0785     char *start = data;
0786 
0787     // Protect leading space
0788     if (s[0] == ' ' && type != GroupString) {
0789         *data++ = '\\';
0790         *data++ = 's';
0791         ++i;
0792     }
0793     Utf8Char utf8;
0794 
0795     for (; i < len; ++i) {
0796         switch (s[i]) {
0797         default:
0798             if (utf8.addByte(s[i])) {
0799                 break;
0800             } else {
0801                 data = utf8.escapeBytes(data);
0802             }
0803             // The \n, \t, \r cases (all < 32) are handled below; we can ignore them here
0804             if (((unsigned char)s[i]) < 32) {
0805                 goto doEscape;
0806             }
0807             // GroupString and KeyString should be valid UTF-8, but ValueString
0808             // can be a bytearray with non-UTF-8 bytes that should be escaped.
0809             if (type == ValueString && ((unsigned char)s[i]) >= 127) {
0810                 goto doEscape;
0811             }
0812             *data++ = s[i];
0813             break;
0814         case '\n':
0815             *data++ = '\\';
0816             *data++ = 'n';
0817             break;
0818         case '\t':
0819             *data++ = '\\';
0820             *data++ = 't';
0821             break;
0822         case '\r':
0823             *data++ = '\\';
0824             *data++ = 'r';
0825             break;
0826         case '\\':
0827             *data++ = '\\';
0828             *data++ = '\\';
0829             break;
0830         case '=':
0831             if (type != KeyString) {
0832                 *data++ = s[i];
0833                 break;
0834             }
0835             goto doEscape;
0836         case '[':
0837         case ']':
0838             // Above chars are OK to put in *value* strings as plaintext
0839             if (type == ValueString) {
0840                 *data++ = s[i];
0841                 break;
0842             }
0843         doEscape:
0844             data = escapeByte(data, s[i]);
0845             break;
0846         }
0847         if (utf8.isComplete()) {
0848             data = utf8.writeUtf8(data);
0849         }
0850     }
0851     data = utf8.write(data);
0852     *data = 0;
0853     result.resize(data - start);
0854 
0855     // Protect trailing space
0856     if (result.endsWith(' ') && type != GroupString) {
0857         result.replace(result.length() - 1, 1, "\\s");
0858     }
0859 
0860     return result;
0861 }
0862 
0863 char KConfigIniBackend::charFromHex(const char *str, const QFile &file, int line)
0864 {
0865     unsigned char ret = 0;
0866     for (int i = 0; i < 2; i++) {
0867         ret <<= 4;
0868         quint8 c = quint8(str[i]);
0869 
0870         if (c >= '0' && c <= '9') {
0871             ret |= c - '0';
0872         } else if (c >= 'a' && c <= 'f') {
0873             ret |= c - 'a' + 0x0a;
0874         } else if (c >= 'A' && c <= 'F') {
0875             ret |= c - 'A' + 0x0a;
0876         } else {
0877             QByteArray e(str, 2);
0878             e.prepend("\\x");
0879             qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) << "Invalid hex character " << c << " in \\x<nn>-type escape sequence \"" << e.constData()
0880                                         << "\".";
0881             return 'x';
0882         }
0883     }
0884     return char(ret);
0885 }
0886 
0887 void KConfigIniBackend::printableToString(BufferFragment *aString, const QFile &file, int line)
0888 {
0889     if (aString->isEmpty() || aString->indexOf('\\') == -1) {
0890         return;
0891     }
0892     aString->trim();
0893     int l = aString->length();
0894     char *r = aString->data();
0895     char *str = r;
0896 
0897     for (int i = 0; i < l; i++, r++) {
0898         if (str[i] != '\\') {
0899             *r = str[i];
0900         } else {
0901             // Probable escape sequence
0902             ++i;
0903             if (i >= l) { // Line ends after backslash - stop.
0904                 *r = '\\';
0905                 break;
0906             }
0907 
0908             switch (str[i]) {
0909             case 's':
0910                 *r = ' ';
0911                 break;
0912             case 't':
0913                 *r = '\t';
0914                 break;
0915             case 'n':
0916                 *r = '\n';
0917                 break;
0918             case 'r':
0919                 *r = '\r';
0920                 break;
0921             case '\\':
0922                 *r = '\\';
0923                 break;
0924             case ';':
0925                 // not really an escape sequence, but allowed in .desktop files, don't strip '\;' from the string
0926                 *r = '\\';
0927                 ++r;
0928                 *r = ';';
0929                 break;
0930             case ',':
0931                 // not really an escape sequence, but allowed in .desktop files, don't strip '\,' from the string
0932                 *r = '\\';
0933                 ++r;
0934                 *r = ',';
0935                 break;
0936             case 'x':
0937                 if (i + 2 < l) {
0938                     *r = charFromHex(str + i + 1, file, line);
0939                     i += 2;
0940                 } else {
0941                     *r = 'x';
0942                     i = l - 1;
0943                 }
0944                 break;
0945             default:
0946                 *r = '\\';
0947                 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) << QStringLiteral("Invalid escape sequence \"\\%1\".").arg(str[i]);
0948             }
0949         }
0950     }
0951     aString->truncate(r - aString->constData());
0952 }
0953 
0954 #include "moc_kconfigini_p.cpp"