10#include "kconfigini_p.h"
12#include "kconfig_core_log_settings.h"
13#include "kconfigdata_p.h"
22#include <QStandardPaths>
23#include <qplatformdefs.h>
31using namespace Qt::StringLiterals;
33KCONFIGCORE_EXPORT
bool kde_kiosk_exception =
false;
45QString KConfigIniBackend::warningProlog(
const QFile &file,
int line)
49 return QStringLiteral(
"KConfigIni: In file %2, line %1:").
arg(line).
arg(file.
fileName());
52KConfigIniBackend::KConfigIniBackend()
56KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options)
58 return parseConfig(currentLocale, entryMap, options,
false);
63KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options,
bool merging)
65 if (filePath().isEmpty()) {
69 QFile file(filePath());
71 return file.
exists() ? ParseOpenError : ParseOk;
76 bool fileOptionImmutable =
false;
77 bool groupOptionImmutable =
false;
78 bool groupSkip =
false;
86 const int langIdx = currentLocale.
indexOf(
'_');
87 const QByteArray currentLanguage = langIdx >= 0 ? currentLocale.
left(langIdx) : currentLocale;
89 QString currentGroup = QStringLiteral(
"<default>");
90 bool bDefault = options & ParseDefaults;
91 bool allowExecutableValues = options & ParseExpansions;
101 while (!contents.isEmpty()) {
103 if (
const auto idx = contents.indexOf(
'\n'); idx < 0) {
107 line = contents.
left(idx);
108 contents = contents.
mid(idx + 1);
114 if (line.
isEmpty() || line.
at(0) ==
'#') {
118 if (line.
at(0) ==
'[') {
119 groupOptionImmutable = fileOptionImmutable;
127 if (end == line.
length()) {
128 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid group header.";
132 if (line.
at(end) ==
']') {
138 if (end + 1 == line.
length()
141 && line.
at(
start + 1) ==
'i') {
143 fileOptionImmutable = !kde_kiosk_exception;
145 groupOptionImmutable = !kde_kiosk_exception;
152 printableToString(namePart, file, lineNo);
155 }
while ((
start = end + 2) <= line.
length() && line.
at(end + 1) ==
'[');
158 groupSkip = entryMap.getEntryOption(currentGroup, {}, {}, KEntryMap::EntryImmutable);
160 if (groupSkip && !bDefault) {
164 if (groupOptionImmutable)
168 immutableGroups.
append(currentGroup);
171 if (groupSkip && !bDefault) {
183 line = line.
mid(eqpos + 1);
187 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (empty key)";
191 KEntryMap::EntryOptions entryOptions = {};
192 if (groupOptionImmutable) {
193 entryOptions |= KEntryMap::EntryImmutable;
201 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing ']')";
203 }
else if (end >
start + 1 && aKey.
at(
start + 1) ==
'$') {
206 switch (aKey.
at(i)) {
208 if (!kde_kiosk_exception) {
209 entryOptions |= KEntryMap::EntryImmutable;
213 if (allowExecutableValues) {
214 entryOptions |= KEntryMap::EntryExpansion;
218 entryOptions |= KEntryMap::EntryDeleted;
220 printableToString(aKey, file, lineNo);
230 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (second locale!?)";
239 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing '=')";
242 printableToString(aKey, file, lineNo);
244 if (locale != currentLocale && locale != currentLanguage) {
246 if (locale.
at(0) !=
'C' || currentLocale !=
"en_US") {
248 entryOptions |= KEntryMap::EntryRawKey;
256 if (options & ParseGlobal) {
257 entryOptions |= KEntryMap::EntryGlobal;
260 entryOptions |= KEntryMap::EntryDefault;
263 entryOptions |= KEntryMap::EntryLocalized;
264 if (locale.
indexOf(
'_') != -1) {
265 entryOptions |= KEntryMap::EntryLocalizedCountry;
268 printableToString(line, file, lineNo);
269 if (entryOptions & KEntryMap::EntryRawKey) {
274 entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions);
276 entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions);
284 for (
const QString &group : std::as_const(immutableGroups)) {
288 return fileOptionImmutable ? ParseImmutable : ParseOk;
291void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map,
bool defaultGroup,
bool primaryGroup,
bool &firstEntry)
294 bool groupIsImmutable =
false;
295 for (
const auto &[key, entry] : map) {
297 if ((key.mGroup != QStringLiteral(
"<default>")) == defaultGroup) {
301 if ((mPrimaryGroup.isNull() || key.mGroup != mPrimaryGroup) == primaryGroup) {
306 if (key.mKey.isNull()) {
307 groupIsImmutable = entry.bImmutable;
311 const KEntry ¤tEntry = entry;
312 if (!defaultGroup && currentGroup != key.mGroup) {
316 currentGroup = key.mGroup;
321 int cgl = currentGroup.
length();
323 for (
int i =
start + 1; i < cgl; i++) {
324 const QChar c = currentGroup.
at(i);
336 if (groupIsImmutable) {
337 file.
write(
"[$i]", 4);
352 file.
write(key.mKey);
354 file.
write(stringToPrintable(key.mKey, KeyString));
355 if (key.bLocal && locale !=
"C") {
361 if (currentEntry.bDeleted) {
362 if (currentEntry.bImmutable) {
363 file.
write(
"[$di]", 5);
365 file.
write(
"[$d]", 4);
368 if (currentEntry.bImmutable || currentEntry.bExpand) {
370 if (currentEntry.bImmutable) {
373 if (currentEntry.bExpand) {
379 file.
write(stringToPrintable(currentEntry.mValue, ValueString));
385void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map)
387 bool firstEntry =
true;
390 writeEntries(locale, file, map,
true,
false, firstEntry);
392 if (!mPrimaryGroup.isNull()) {
394 writeEntries(locale, file, map,
false,
true, firstEntry);
398 writeEntries(locale, file, map,
false,
false, firstEntry);
401bool KConfigIniBackend::writeConfig(
const QByteArray &locale, KEntryMap &entryMap, WriteOptions options)
403 Q_ASSERT(!filePath().isEmpty());
406 const bool bGlobal = options & WriteGlobal;
411 ParseOptions opts = ParseExpansions;
415 ParseInfo info = parseConfig(locale, writeMap, opts,
true);
416 if (info != ParseOk) {
421 for (
auto &[key, entry] : entryMap) {
422 if (!key.mKey.isEmpty() && !entry.bDirty) {
427 if (entry.bGlobal == bGlobal) {
428 if (entry.bReverted && entry.bOverridesGlobal) {
429 entry.bDeleted =
true;
430 writeMap[key] = entry;
431 }
else if (entry.bReverted) {
433 }
else if (!entry.bDeleted) {
434 writeMap[key] = entry;
436 KEntryKey defaultKey = key;
437 defaultKey.bDefault =
true;
438 if (entryMap.find(defaultKey) == entryMap.end() && !entry.bOverridesGlobal) {
442 writeMap[key] = entry;
446 entry.bDirty =
false;
457 bool createNew =
true;
465 if (fi.ownerId() == ::getuid()) {
467 fileMode = fi.permissions();
482 file.setDirectWriteFallback(
true);
484 qWarning(KCONFIG_CORE_LOG) <<
"Couldn't create a new file:" << filePath() <<
". Error:" << file.
errorString();
488 qWarning(KCONFIG_CORE_LOG) <<
"Couldn't create a new file:" << filePath() <<
". Error:" << file.
errorString();
494 writeEntries(locale, file, writeMap);
498 file.cancelWriting();
514 qCWarning(KCONFIG_CORE_LOG) <<
"Couldn't write" << filePath() <<
". Disk full?";
525 f.setTextModeEnabled(
true);
526 writeEntries(locale, f, writeMap);
531bool KConfigIniBackend::isWritable()
const
533 const QString filePath = this->filePath();
545 while (!
dir.exists()) {
547 if (parent ==
dir.filePath()) {
553 return dir.isDir() &&
dir.isWritable();
556QString KConfigIniBackend::nonWritableErrorMessage()
const
558 return tr(
"Configuration file \"%1\" not writable.\n").
arg(filePath());
561void KConfigIniBackend::createEnclosing()
563 const QString file = filePath();
572void KConfigIniBackend::setFilePath(
const QString &path)
582 setLocalFilePath(info.canonicalFilePath());
586 if (
QString filePath = info.dir().canonicalPath(); !filePath.
isEmpty()) {
588 setLocalFilePath(filePath);
590 setLocalFilePath(path);
596 if (filePath().isEmpty()) {
597 return KConfigBase::NoAccess;
601 return KConfigBase::ReadWrite;
604 return KConfigBase::ReadOnly;
607bool KConfigIniBackend::lock()
609 Q_ASSERT(!filePath().isEmpty());
622 lockFile = std::make_unique<QLockFile>(filePath() +
QLatin1String(
".lock"));
627 lockFile = std::make_unique<QLockFile>(filePath() +
QLatin1String(
".lock"));
631 if (!lockFile->lock()) {
635 return lockFile->isLocked();
638void KConfigIniBackend::unlock()
645bool KConfigIniBackend::isLocked()
const
647 return lockFile && lockFile->isLocked();
654char *escapeByte(
char *data,
unsigned char s)
656 static const char nibbleLookup[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f'};
659 *data++ = nibbleLookup[s >> 4];
660 *data++ = nibbleLookup[s & 0x0f];
669 unsigned char bytes[4];
671 unsigned char charLength;
684 bool addByte(
unsigned char b)
687 if (b > 0xc1 && (b & 0xe0) == 0xc0) {
689 }
else if ((b & 0xf0) == 0xe0) {
691 }
else if (b < 0xf5 && (b & 0xf8) == 0xf0) {
698 }
else if (count < 4 && (b & 0xc0) == 0x80) {
700 if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) {
703 if (charLength == 4) {
704 if (bytes[0] == 0xf0 && b < 0x90) {
707 if (bytes[0] == 0xf4 && b > 0x8f) {
719 bool isComplete()
const
721 return count > 0 && count == charLength;
724 char *escapeBytes(
char *data)
726 for (
unsigned char i = 0; i < count; ++i) {
727 data = escapeByte(data, bytes[i]);
734 char *writeUtf8(
char *data)
736 for (
unsigned char i = 0; i < count; ++i) {
746 char *write(
char *data)
749 data = writeUtf8(data);
751 data = escapeBytes(data);
760 const int len = aString.
size();
769 char *data = result.
data();
773 if (s[0] ==
' ' && type != GroupString) {
780 for (; i < len; ++i) {
783 if (utf8.addByte(s[i])) {
786 data = utf8.escapeBytes(data);
789 if (((
unsigned char)s[i]) < 32) {
794 if (type == ValueString && ((
unsigned char)s[i]) >= 127) {
816 if (type != KeyString) {
824 if (type == ValueString) {
829 data = escapeByte(data, s[i]);
832 if (utf8.isComplete()) {
833 data = utf8.writeUtf8(data);
836 data = utf8.write(data);
841 if (result.
endsWith(
' ') && type != GroupString) {
848char KConfigIniBackend::charFromHex(
const char *str,
const QFile &file,
int line)
850 unsigned char ret = 0;
851 for (
int i = 0; i < 2; i++) {
853 quint8 c = quint8(str[i]);
855 if (c >=
'0' && c <=
'9') {
857 }
else if (c >=
'a' && c <=
'f') {
858 ret |= c -
'a' + 0x0a;
859 }
else if (c >=
'A' && c <=
'F') {
860 ret |= c -
'A' + 0x0a;
864 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) <<
"Invalid hex character " << c <<
" in \\x<nn>-type escape sequence \"" << e.constData()
872void KConfigIniBackend::printableToString(
QByteArrayView &aString,
const QFile &file,
int line)
878 int l = aString.
size();
879 char *r =
const_cast<char *
>(aString.
data());
882 for (
int i = 0; i < l; i++, r++) {
883 if (str[i] !=
'\\') {
923 *r = charFromHex(str + i + 1, file, line);
932 qCWarning(KCONFIG_CORE_LOG).noquote() << warningProlog(file, line) << QStringLiteral(
"Invalid escape sequence: «\\%1»").arg(str[i]);
939QString KConfigIniBackend::filePath()
const
941 return mLocalFilePath;
944void KConfigIniBackend::setLocalFilePath(
const QString &file)
946 mLocalFilePath = file;
949void KConfigIniBackend::setPrimaryGroup(
const QString &group)
951 mPrimaryGroup = group;
954#include "moc_kconfigini_p.cpp"
AccessMode
Possible return values for accessMode().
Q_SCRIPTABLE QString start(QString train="")
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QByteArray & append(QByteArrayView data)
const char * constData() const const
bool endsWith(QByteArrayView bv) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray left(qsizetype len) const const
qsizetype length() const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
void reserve(qsizetype size)
void resize(qsizetype newSize, char c)
qsizetype size() const const
QByteArrayView left(qsizetype length) const const
QByteArrayView mid(qsizetype start, qsizetype length) const const
char at(qsizetype n) const const
const_pointer constData() const const
const_pointer data() const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
bool isNull() const const
qsizetype lastIndexOf(QByteArrayView bv) const const
qsizetype length() const const
qsizetype size() const const
QByteArray toByteArray() const const
QByteArrayView trimmed() const const
void truncate(qsizetype length)
bool isAbsolutePath(const QString &path)
bool mkpath(const QString &dirPath) const const
bool exists(const QString &fileName)
virtual QString fileName() const const override
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
virtual bool setPermissions(Permissions permissions) override
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
iterator insert(const Key &key, const T &value)
void reserve(qsizetype size)
QString errorString() const const
bool isWritable() const const
virtual bool open(QIODeviceBase::OpenMode mode)
void setTextModeEnabled(bool enabled)
virtual qint64 size() const const
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
QString writableLocation(StandardLocation type)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const